| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- '''
- @author: olivier.massot, 2019
- '''
- import heapq
- import sys
- DEBUG = True
- def log(x):
- if DEBUG: print(x, file=sys.stderr)
- # -- Base class
- class Base():
- def __repr__(self):
- return f"<{self.__class__.__name__}: {self.__dict__}>"
- # --- locations
- class Location(Base):
- name = ""
- passable = False
- class SpecialLocation(Location):
- pass
- class DishWasher(SpecialLocation):
- pass
- class IcecreamCrate(SpecialLocation):
- pass
- class BlueberriesCrate(SpecialLocation):
- pass
- class StrawberriesCrate(SpecialLocation):
- pass
- class DoughCrate(SpecialLocation):
- pass
- class ChoppingBoard(SpecialLocation):
- pass
- class Oven(SpecialLocation):
- def __init__(self):
- self._content = None
- self._timer = 0
- @property
- def content(self):
- return self._content
-
- @content.setter
- def content(self, content):
- self._content = match[content]
-
- @property
- def timer(self):
- return self._timer
-
- @timer.setter
- def timer(self, timer):
- self._timer = int(timer)
-
- def update(self, content, timer):
- self.content = content
- self.timer = timer
- oven = Oven()
- class Window(SpecialLocation):
- pass
- class Start(Location):
- passable = True
- class Start0(Start): pass
- class Start1(Start): pass
- class FloorCell(Location):
- passable = True
- class EmptyTable(Location):
- pass
- locations = [DishWasher, IcecreamCrate, BlueberriesCrate, StrawberriesCrate, DoughCrate, ChoppingBoard, Oven, Window, Start0, Start1, FloorCell, EmptyTable]
- special_locations = [l for l in locations if l is SpecialLocation]
- tables = []
- # -- Grid
- class PathNode(tuple):
- def __new__(self, x, y, parent=None):
- n = tuple.__new__(self, (x, y))
- n.parent = parent
- n.cost = 0
- return n
- class Grid(Base):
- def __init__(self, cells):
- self.cells = cells
- self.w, self.h = len(cells[0]), len(cells)
- self.add_costs = {}
- def at(self, x, y):
- return self.cells[y][x]
- def flatten(self):
- return [(x, y, c) for y, row in enumerate(self.cells) for x, c in enumerate(row)]
- @property
- def coords(self):
- return [(x, y) for y in range(self.h) for x in range(self.w)]
- def where_are(self, content):
- return [(x, y) for x, y, c in self.flatten() if c is content]
- @staticmethod
- def distance(from_, to_):
- return abs(from_[0] - to_[0]) + abs(from_[1] - to_[1])
- def items(self):
- return [c for row in self.cells for c in row]
- def closest_in(self, from_, coords):
- return sorted([(c, Grid.distance(from_, c)) for c in coords], key=lambda k: k[1])[0]
- def closest(self, from_, content):
- return self.closest_in(from_, self.where_are(content))
- def neighbors(self, x, y, diags=True):
- neighs = [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
- if diags:
- neighs += [(x - 1, y - 1), (x + 1, y - 1), (x - 1, y + 1), (x + 1, y + 1)]
- return [(x, y) for x, y in neighs if 0 <= x < self.w and 0 <= y < self.h]
- def passable(self, x, y):
- return self.at(x, y).passable and self.cost(x, y) < 50
- def cost(self, x, y):
- return 10 + self.add_costs.get((x, y), 0)
- def path(self, origin, target, incl_start=False):
- nodes = []
- origin = PathNode(*origin)
- targets = grid.neighbors(*target)
- heapq.heappush(nodes, (0, origin))
- break_on = 10000
- iteration = 0
- while nodes:
- current = heapq.heappop(nodes)[1]
- if current in targets:
- path = []
- next_ = current
- while next_:
- if next_ != origin or incl_start:
- path.insert(0, next_)
- next_ = next_.parent
- return path
- neighbors = self.neighbors(*current, False)
- for x, y in neighbors:
- iteration += 1
- if iteration >= break_on:
- return None
- if not self.passable(x, y):
- continue
- cost = current.cost + self.cost(x, y)
- priority = cost + 10 * (abs(x - target[0]) + abs(y - target[1]))
- node = PathNode(x, y, current)
- node.cost = cost
- heapq.heappush(nodes, (priority, node))
- else:
- return None
- ## -- objects
- class Recipe(Base):
- name = ""
- location = None
- cooking_time = 0
- requirements = []
- needs_free_hands = False
-
- def available(self):
- return []
-
- def get(self):
- pass
-
- class FinalProduct(Recipe):
- needs_dish = True
-
- class Ingredient(Recipe):
- needs_free_hands = True
- class Dish(Recipe):
- name = "DISH"
- location = DishWasher
- class IceCream(FinalProduct):
- name = "ICE_CREAM"
- location = IcecreamCrate
- class Blueberries(FinalProduct):
- name = "BLUEBERRIES"
- location = BlueberriesCrate
-
- class Strawberries(Ingredient):
- name = "STRAWBERRIES"
- location = StrawberriesCrate
- needs_free_hands = True
-
- class ChoppedStrawberries(FinalProduct):
- name = "CHOPPED_STRAWBERRIES"
- location = ChoppingBoard
- requirements = [Strawberries]
-
- class Dough(Ingredient):
- name = "DOUGH"
- location = DoughCrate
- needs_free_hands = True
- class Croissant(FinalProduct):
- name = "CROISSANT"
- requirements = [Dough]
- location = Oven
- cooking_time = 10
-
- class ChoppedDough(Ingredient):
- name = "CHOPPED_DOUGH"
- requirements = [Dough]
- location = ChoppingBoard
-
- class RawTart(Ingredient):
- name = "RAW_TART"
- requirements = [ChoppedDough]
- location = BlueberriesCrate
-
- class Tart(FinalProduct):
- name = "TART"
- requirements = [RawTart]
- location = Oven
- cooking_time = 10
- # -- Other
- class Customer(Base):
- def __init__(self, item, award):
- self.order = [match[i] for i in item.split('-')]
- self.award = int(award)
- class Table(Base):
- def __init__(self, x, y, item):
- self.x = int(x)
- self.y = int(y)
- self.item = [match[i] for i in item.split('-')]
- @property
- def pos(self):
- return (self.x, self.y)
- # --- Actions
- class Action(Base):
- needs_dish = False
- needs_free_hands = False
-
- def __init__(self, location):
- self.location = location
-
- def locate(self, player):
- self.pos, self.dist = grid.closest(player.pos, self.location)
-
- class GetAction(Action):
-
- def __init__(self, subject):
- self.subject = subject
- self.location = self.subject.location
-
- def locate(self, player):
- from_crate = grid.where_are(self.location)
- on_tables = [t.pos for t in tables if [self.subject] == t.item]
- self.pos, self.dist = grid.closest_in(player.pos, from_crate + on_tables)
-
- class GetDessert(GetAction):
- needs_dish = True
-
- class GetIngredient(GetAction):
- needs_free_hands = True
-
- class GetDish(GetAction):
- def __init__(self):
- super().__init__(Dish)
-
- class Transform(Action):
- pass
-
- class Cook(Action):
- def __init__(self):
- self.location = Oven
-
- class GetCookedDessert(GetDessert):
- def __init__(self, subject, ready_in=0):
- super().__init__(subject)
- self.location = Oven
- self.ready_in = ready_in
- class Deliver(Action):
- def __init__(self):
- self.location = Window
-
- class DropHanded(Action):
- def __init__(self):
- self.subject = None
- self.location = EmptyTable
-
- def locate(self, player):
- free_tables = [c for c in grid.where_are(self.location) if not c in [(t.x, t.y) for t in tables]]
- self.pos, self.dist = grid.closest_in(player.pos, free_tables)
- class GetOnTable(GetAction):
- def __init__(self, tables):
- self._tables = tables
-
- def locate(self, player):
- self.pos, self.dist = grid.closest_in(player.pos, [t.pos for t in self._tables])
- class GetIngredientOnTable(GetOnTable):
- needs_free_hands = True
- class GetDishOnTable(GetOnTable):
- pass
- class Cooker(Base):
- def __init__(self):
- self._x = -1
- self._y = -1
- self._in_hand = []
- self.order = []
- @property
- def x(self):
- return self._x
- @x.setter
- def x(self, x):
- self._x = int(x)
- @property
- def y(self):
- return self._y
- @y.setter
- def y(self, y):
- self._y = int(y)
- @property
- def pos(self):
- return (self.x, self.y)
- @property
- def in_hand(self):
- return self._in_hand
- @in_hand.setter
- def in_hand(self, item):
- self._in_hand = [x for x in [match[i] for i in item.split('-')] if x is not None]
- def take_order(self, order):
- self.order = order
- def order_fullfilled(self):
- self.order = []
- def update(self, x, y, item):
- self.x = x
- self.y = y
- self.in_hand = item
- @property
- def hands_free(self):
- return len(self._in_hand) == 0
- @property
- def hands_full(self):
- return any(recipe.needs_free_hands for recipe in self._in_hand)
- @property
- def dish_handed(self):
- return Dish in self.in_hand
- def eval_orders(self, customers):
- waiting = sorted(customers, reverse=True, key=lambda x: x.award)
- self.take_order(waiting[0].order)
-
- def get_recipe(self, recipe):
-
- on_tables = [t for t in tables if t.item == [recipe]]
- if on_tables:
- if issubclass(recipe, Ingredient):
- return GetIngredientOnTable(on_tables)
- else:
- return GetOnTable(on_tables)
-
- elif recipe.cooking_time and (recipe == oven.content or (any((req == oven.content) for req in recipe.requirements) and oven.timer < 3)):
- return GetCookedDessert(recipe)
-
- else:
- # check requirements
-
- if recipe.requirements:
-
- if all((req in self.in_hand) for req in recipe.requirements):
- if recipe.cooking_time:
- return Cook()
- else:
- return Transform(recipe.location)
-
- else:
-
- for req in recipe.requirements:
- if not req in self.in_hand and not req == oven.content:
- return self.get_recipe(req)
-
- else:
- # no requirements, simple get
- if issubclass(recipe, Ingredient):
- return GetIngredient(recipe)
- elif issubclass(recipe, Dish):
- return GetDish()
- else:
- return GetAction(recipe)
-
- def todo(self):
- todo = []
-
- # nothing left to do: deliver
- if all((i in self.in_hand) for i in self.order):
- todo = [Deliver()]
-
- store = None
- for table in tables:
- if Dish in table.item and all((i in self.order and not i in self.in_hand) for i in table.item):
- store = table
-
- for item in self.order:
- if item in self.in_hand or (store and item in store.item):
- # already done
- continue
-
- action = self.get_recipe( item )
- if action:
- todo.append( action )
-
- if store:
- todo.append(GetDishOnTable([store]))
-
- for action in todo:
- action.locate(self)
-
- return todo
- def act(self, action):
- if isinstance(action, Deliver) and action.pos in grid.neighbors(*self.pos):
- # we're about to deliver, clean the order
- self.order = []
-
- elif (action.needs_free_hands and not self.hands_free) or (isinstance(action, GetAction) and self.hands_full):
- # cannot act, needs to drop
- action = DropHanded()
- action.locate(self)
- log(f"Need to drop before: {action.pos}")
-
- self.use(*action.pos)
- def use(self, x, y, msg=""):
- print("USE", x, y, msg)
- def move(self, x, y):
- print("MOVE", x, y)
- def wait(self):
- print("WAIT")
- # --- constants
- match = {
- '0': Start0,
- '1': Start1,
- 'B': BlueberriesCrate,
- 'I': IcecreamCrate,
- 'S': StrawberriesCrate,
- 'C': ChoppingBoard,
- 'H': DoughCrate,
- 'W': Window,
- '#': EmptyTable,
- 'D': DishWasher,
- '.': FloorCell,
- 'O': Oven,
- 'NONE': None,
- 'DISH': Dish,
- 'ICE_CREAM': IceCream,
- 'BLUEBERRIES': Blueberries,
- 'STRAWBERRIES': Strawberries,
- 'CHOPPED_STRAWBERRIES': ChoppedStrawberries,
- 'DOUGH': Dough,
- 'CROISSANT': Croissant,
- 'CHOPPED_DOUGH': ChoppedDough,
- 'RAW_TART': RawTart,
- 'TART': Tart,
- }
- # --- input vars
- num_all_customers = int(input())
- all_customers = [Customer(*input().split()) for _ in range(num_all_customers)]
- grid = Grid([[match[c] for c in input()] for i in range(7)])
- log(f"{num_all_customers} customers: {all_customers}")
- log(f"grid: {grid}")
- player = Cooker()
- partner = Cooker()
- oven = next((i for i in grid.items() if type(i) is Oven), Oven())
- path = []
- blocked_since = 0
- previous_partner_pos = None
- partner_did_not_moved_since = 0
- while True:
- # <--- turn input
- turns_remaining = int(input())
- player.update(*input().split())
- log(f"*** player: {player}")
- partner.update(*input().split())
- log(f"*** partner: {partner}")
- if partner.pos != previous_partner_pos:
- previous_partner_pos = partner.pos
- partner_did_not_moved_since = 0
- else:
- partner_did_not_moved_since += 1
- log(f"partner did not moved since: {partner_did_not_moved_since}")
-
- num_tables_with_items = int(input()) # the number of tables in the kitchen that currently hold an item
- tables = [Table(*input().split()) for _ in range(num_tables_with_items)]
- log(f"*** tables: {tables}")
- oven.update(*input().split())
- log(f"*** oven: {oven}")
- num_customers = int(input()) # the number of customers currently waiting for food
- customers = [Customer(*input().split()) for _ in range(num_customers)]
- log(f"*** customers: {customers}")
- # --->
- # ## SCRIPT
- # if no current order, take the most awarded
- if not player.order or not player.order in [c.order for c in customers]:
- player.eval_orders(customers)
- log('>>> new order taken')
- log(f"order: {player.order}")
- todo = player.todo()
- log(f"todo: {todo}")
-
- priority = [
- # order fulfilled: deliver
- next((a for a in todo if type(a) is Deliver), None),
-
- # cook has a dessert in its hands and no dish, he have to take one
- next((a for a in todo if isinstance(a, GetDish) and any(r for r in player.in_hand if issubclass(r, FinalProduct))), None),
-
- # something to take out from the oven!
- next((a for a in todo if type(a) is GetCookedDessert and a.ready_in < 3), None),
-
- # If cook has an ingredient in hands, he needs to prepare it
- next((a for a in todo if type(a) is Transform), None),
-
- # If cook has an cook_ingredient in hands, he needs to cook it
- next((a for a in todo if type(a) is Cook and not oven.content), None),
-
- # If hands are free and an ingredient is needed, we go for it first
- next((a for a in todo if a.needs_free_hands and player.hands_free), None),
-
- # there is a dish waiting on a table
- next((a for a in todo if type(a) is GetDishOnTable), None),
-
- # get the closest action
- next((a for a in sorted(todo, key=lambda x: x.dist) if (type(a) != GetCookedDessert or a.ready_in < 3)), None),
-
- # wait for the oven to finish
- next((a for a in todo if type(a) is GetCookedDessert), None),
-
- ]
-
- log(f"priorities: {priority}")
- priority = [p for p in priority if not p is None]
-
- if not priority:
- log("nothing to do??")
- player.wait()
- continue
-
- next_task = priority[0]
-
- log(f"next_task: {next_task}")
-
- # <--- Update moving costs
- # update grid movement costs following the probability of finding the partner here
- 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]
- grid.add_costs = {}
- for x, y in partner_could_be_there:
- k1 = (2 + (5*partner_did_not_moved_since)) if (x, y) == partner.pos else 1
- # cell is next to a special cell, partner has more chance to stop there
- k2 = 2 if any((c for c in grid.neighbors(x, y) if isinstance(grid.at(*c), SpecialLocation))) else 1
- grid.add_costs[(x, y)] = k1 * k2 * 3
- log(f"add_costs: {grid.add_costs}")
- # --->
-
- # <--- compute shortest path
- previous_path = path
- path = grid.path(player.pos, next_task.pos)
- log(f"path: {path}")
- # --->
-
- # <-- try to avoid blocking situations
- if path and path == previous_path:
- log(f">> path did not change, blocked? turn: {blocked_since}")
- blocked_since += 1
- else:
- blocked_since = 0
- if blocked_since > 1:
- aside = next((c for c in grid.neighbors(*player.pos) if grid.passable(*c) and not c == partner.pos), None)
- if aside:
- blocked_since = 0
- log("step aside")
- player.move(*aside)
- continue
- else:
- log("ouch, cannot move aside")
-
- # --->
-
- if path is not None:
- if len(path) > 0:
- if len(path) > 4:
- player.move(*path[3])
- else:
- player.move(*path[-1])
- else:
- player.act(next_task)
- else:
- player.act(next_task)
|