Advertisement
philRG

FC 2020 (phil's version without bfs) bronze

Apr 5th, 2022
1,421
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 16.33 KB | None | 0 0
  1. import math
  2. import random
  3. import sys
  4. import time
  5. from copy import copy
  6. from typing import Tuple, List, Optional
  7. # from collections import deque
  8. # from numpy import array, zeros
  9. from enum import Enum
  10.  
  11.  
  12. class Color(Enum):
  13.     BLUE = 0
  14.     GREEN = 1
  15.     ORANGE = 2
  16.     YELLOW = 3
  17.  
  18.  
  19. def idebug(*args):
  20.     return
  21.     print(*args, file=sys.stderr, flush=True)
  22.  
  23.  
  24. def debug(*args):
  25.     # return
  26.     print(*args, file=sys.stderr, flush=True)
  27.  
  28.  
  29. class TomeSpell:
  30.     def __init__(self, tome_spell_id: int, delta: List[int], castable: int, repeatable: int, tome_index: int, tax_count: int):
  31.         self.tome_spell_id = tome_spell_id
  32.         self.delta = delta
  33.         self.castable = castable
  34.         self.repeatable = repeatable
  35.         self.tome_index = tome_index
  36.         self.tax_count = tax_count
  37.  
  38.     @property
  39.     def id(self):
  40.         return self.tome_spell_id
  41.  
  42.     def __copy__(self):
  43.         return Spell(self.tome_spell_id, self.delta.copy(), self.castable, self.repeatable)
  44.  
  45.     def __hash__(self):
  46.         return hash((str(self.delta), self.castable, self.repeatable))
  47.  
  48.     def __repr__(self):
  49.         # return f'{self.cast_id} {str(self.delta)} {self.castable}'
  50.         return f'{self.tome_spell_id} {self.castable} {self.repeatable} {self.tome_index} {self.tax_count}'
  51.  
  52.  
  53. class Spell:
  54.     def __init__(self, cast_id: int, delta: List[int], castable: int, repeatable: int):
  55.         self.cast_id = cast_id
  56.         self.delta = delta
  57.         self.castable = castable
  58.         self.repeatable = repeatable
  59.  
  60.     @property
  61.     def id(self):
  62.         return self.cast_id
  63.  
  64.     def __copy__(self):
  65.         return Spell(self.cast_id, self.delta.copy(), self.castable, self.repeatable)
  66.  
  67.     def __hash__(self):
  68.         return hash((str(self.delta), self.castable, self.repeatable))
  69.  
  70.     def __repr__(self):
  71.         return f'{self.cast_id} {str(self.delta)} {self.castable}'
  72.         # return f'{self.cast_id} {self.castable}'
  73.  
  74.  
  75. class Potion:
  76.     def __init__(self, potion_id: int, delta: List[int], price: int, bonus: int, bonus_count: int):
  77.         self.potion_id = potion_id
  78.         self.delta = delta
  79.         self.price = price
  80.         self.bonus = bonus
  81.         self.bonus_count = bonus_count
  82.  
  83.     @property
  84.     def id(self):
  85.         return self.potion_id
  86.  
  87.     def __repr__(self):
  88.         return f'{self.potion_id} {str(self.delta)} {self.price + self.bonus}'
  89.  
  90.  
  91. class Witch:
  92.     def __init__(self, witch_id: int):
  93.         self.witch_id = witch_id
  94.         self.inv: List[int] = []
  95.         self.spells: List[Spell] = []
  96.         self.score = 0
  97.  
  98.     @property
  99.     def id(self) -> int:
  100.         return self.witch_id
  101.  
  102.     def rest(self) -> None:
  103.         for s in self.spells:
  104.             s.castable = True
  105.  
  106.     def distance_to(self, p: Potion) -> int:
  107.         """
  108.            Distance to potion p (Calculate the number of actions needed to deliver potion p)
  109.        :param p: potion to deliver
  110.        :return: number of actions
  111.        """
  112.         order: Order = Order(p)
  113.         actions: List[Action] = []
  114.         recipe = {Color(i): 0 for i in range(4)}
  115.         while not order.is_ready():
  116.             ingredient: int = order.get_next_ingredient()
  117.             # cast actions to produce ingredient
  118.             needed_actions: List[Action] = order.get_actions_to_produce_ingredient(ingredient=Color(ingredient), witch=self)
  119.             # debug(f'preparing {len(actions_recipe)} actions for {Color(recipe)}: {actions_recipe}')
  120.             order.ingredients[ingredient] += 1
  121.             actions += needed_actions
  122.             recipe[Color(ingredient)] += 1
  123.         debug(f'{len(actions)} actions needed to produce {recipe} = \n {actions}')
  124.         return len(actions)
  125.  
  126.     def __copy__(self):
  127.         return Witch(self.witch_id, self.inv[:], [copy(s) for s in self.spells])
  128.  
  129.     def __repr__(self):
  130.         return f'{self.witch_id} {self.inv} {self.spells}'
  131.  
  132.  
  133. class Recipe:
  134.     def __init(self, color: Color):
  135.         self.color = color
  136.  
  137.  
  138. class Order:
  139.     def __init__(self, p: Potion):
  140.         self.potion = p
  141.         self.ingredients: List[int] = [0] * 4
  142.  
  143.     def update(self, inv: List[int]) -> None:
  144.         ingredient: int = self.get_next_ingredient()
  145.         inventory: List[int] = inv[:]
  146.         while ingredient is not None:
  147.             # debug(f'recipe color needed for order : {Color(recipe)}')
  148.             if inventory[ingredient] == 0:
  149.                 # debug(f'missing {Color(recipe)} recipe')
  150.                 break
  151.             self.ingredients[ingredient] += 1
  152.             inventory[ingredient] -= 1
  153.             ingredient = self.get_next_ingredient()
  154.  
  155.     def get_next_ingredient(self) -> int:
  156.         """ingredient to produce"""
  157.         ingredient_request: int = [i for i, ing_count in enumerate(self.potion.delta) if -ing_count > self.ingredients[i]]
  158.         return max(ingredient_request) if ingredient_request else None
  159.  
  160.     def is_ready(self) -> bool:
  161.         return sum(add(self.potion.delta, self.ingredients)) == 0
  162.  
  163.     def get_actions_recipe_staging(self, recipe: Color, witch: Witch, actions=[]):
  164.         # List of spells that can produce recipe
  165.         available_spells = [s for s in witch.spells if s.delta[recipe.value] > 0 if s.castable]
  166.         if not available_spells:
  167.             action = Action('REST')
  168.             witch.rest()
  169.             self.get_actions_to_produce_ingredient(recipe, copy(witch), actions + [action])
  170.         else:
  171.             spell_candidates = [(s, r_id * (-count - witch.inv[r_id])) for s in available_spells for r_id, count in enumerate(s.delta) if s.castable and count < 0 and witch.inv[r_id] < -count][0]
  172.             for s in spell_candidates:
  173.                 debug(f'recipe {recipe} can be produced by spell {s.id}')
  174.             # needed_recipes = [(s, Color(r_id), count) for r_id, count in enumerate(s.delta) for s in available_spells if count < 0][0]
  175.             cheapest_spell = min(spell_candidates, key=lambda x: x[1])[0]
  176.             debug(f'recipe {recipe} best spell {cheapest_spell.id}')
  177.             action = Action('CAST', cheapest_spell.id)
  178.  
  179.             self.get_actions_to_produce_ingredient(recipe, copy(witch), actions + [action])
  180.  
  181.     def get_actions_to_produce_ingredient(self, ingredient: Color, witch: Witch):
  182.         """
  183.        :param inv: ingredient
  184.        :param spells:
  185.        :return: list of cast actions needed to prepare recipe
  186.        """
  187.         actions = []
  188.         spells = [copy(s) for s in witch.spells]
  189.         # cinv = {Color.BLUE: inv[0], Color.GREEN: inv[1], Color.ORANGE: inv[2], Color.YELLOW: inv[3]}
  190.         # Spell needed to produce recipe
  191.         first_spell = [s for s in spells if s.delta[ingredient.value] > 0][0]
  192.         if ingredient == Color.BLUE:
  193.             if not first_spell.castable:
  194.                 actions.append('REST')
  195.             actions.append(Action('CAST', first_spell.id))
  196.             return actions
  197.         else:
  198.             # recipe(s) needed to cast spell
  199.             first_ingredient = [Color(r_id) for r_id, count in enumerate(first_spell.delta) if count < 0][0]
  200.             # If we can get a recipe_id from inventory (not yet reserved for the potion)
  201.             if witch.inv[first_ingredient.value] > self.ingredients[first_ingredient.value]:
  202.                 if not first_spell.castable:
  203.                     actions.append('REST')
  204.                 actions.append(Action('CAST', first_spell.id))
  205.                 return actions
  206.             else:
  207.                 # we need to cast a second spell to produce this recipe
  208.                 second_spell = [s for s in spells if s.delta[first_ingredient.value] > 0][0]
  209.                 if first_ingredient == Color.BLUE:
  210.                     if not (first_spell.castable and second_spell.castable):
  211.                         actions.append('REST')
  212.                     actions.append(Action('CAST', second_spell.id))
  213.                     actions.append(Action('CAST', first_spell.id))
  214.                     return actions
  215.                 else:
  216.                     second_ingredient = [Color(r_id) for r_id, count in enumerate(second_spell.delta) if count < 0][0]
  217.                     # If we can get a recipe_id not reserved for the potion
  218.                     if witch.inv[second_ingredient.value] > self.ingredients[second_ingredient.value]:
  219.                         if not (second_spell.castable and first_spell.castable):
  220.                             actions.append('REST')
  221.                         actions.append(Action('CAST', second_spell.id))
  222.                         actions.append(Action('CAST', first_spell.id))
  223.                         return actions
  224.                     else:
  225.                         third_spell = [s for s in spells if s.delta[second_ingredient.value] > 0][0]
  226.                         if second_ingredient == Color.BLUE:
  227.                             # recipe(s) needed to cast spell
  228.                             if not (first_spell.castable and second_spell.castable and third_spell.castable):
  229.                                 actions.append('REST')
  230.                             actions.append(Action('CAST', third_spell.id))
  231.                             actions.append(Action('CAST', second_spell.id))
  232.                             actions.append(Action('CAST', first_spell.id))
  233.                             return actions
  234.                         else:
  235.                             third_ingredient = [Color(r_id) for r_id, count in enumerate(third_spell.delta) if count < 0][0]
  236.                             # If we can get a recipe_id not reserved for the potion
  237.                             if witch.inv[third_ingredient.value] > self.ingredients[third_ingredient.value]:
  238.                                 if not (first_spell.castable and second_spell.castable and third_spell.castable):
  239.                                     actions.append('REST')
  240.                                 actions.append(Action('CAST', third_spell.id))
  241.                                 actions.append(Action('CAST', second_spell.id))
  242.                                 actions.append(Action('CAST', first_spell.id))
  243.                                 return actions
  244.                             else:
  245.                                 fourth_spell = [s for s in spells if s.delta[third_ingredient.value] > 0][0]
  246.                                 # recipe(s) needed to cast spell
  247.                                 if not (first_spell.castable and second_spell.castable and third_spell.castable and fourth_spell.castable):
  248.                                     actions.append('REST')
  249.                                 actions.append(Action('CAST', fourth_spell.id))
  250.                                 actions.append(Action('CAST', third_spell.id))
  251.                                 actions.append(Action('CAST', second_spell.id))
  252.                                 actions.append(Action('CAST', first_spell.id))
  253.                                 return actions
  254.  
  255.     def __repr__(self):
  256.         if not self.is_ready():
  257.             blue = f'blue: {self.ingredients[0]}/{-self.potion.delta[0]}'
  258.             green = f'green: {self.ingredients[1]}/{-self.potion.delta[1]}'
  259.             orange = f'orange: {self.ingredients[2]}/{-self.potion.delta[2]}'
  260.             yellow = f'yellow: {self.ingredients[3]}/{-self.potion.delta[3]}'
  261.             return f'{blue} - {green} - {orange} - {yellow}'
  262.         return f'order {self.potion} READY!'
  263.  
  264.  
  265. class Action:
  266.     def __init__(self, action_type, action_id=None):
  267.         self.action_type = action_type
  268.         self.action_id = action_id
  269.  
  270.     @property
  271.     def id(self):
  272.         return self.action_id
  273.  
  274.     def __hash__(self):
  275.         return hash((self.action_type, self.action_id))
  276.  
  277.     def __repr__(self):
  278.         if self.action_type == 'CAST':
  279.             return f'CAST {self.action_id}'
  280.         elif self.action_type == 'BREW':
  281.             return f'BREW {self.action_id}'
  282.         else:
  283.             return f'REST'
  284.  
  285.  
  286. def select_potion_to_brew(potions: List[Potion], witch: Witch) -> Potion:
  287.     potions_score = []
  288.     # op_witch: Witch = players[0 if witch.witch_id else 1]
  289.     # for p in potions:
  290.     #     my_score = (p.price + p.bonus) / (1 + distance_to(p, witch))
  291.     #     op_score = (p.price + p.bonus) / (1 + distance_to(p, op_witch))
  292.     #     # potions_score.append((p, my_score / (1 + op_score)))
  293.     #     potions_score.append((p, my_score))
  294.     # # potions_score.sort(key=lambda p: p[1], reverse=True)
  295.     # for p, score in potions_score:
  296.     #     debug(f'potion {p} - score = {round(score, 2)}')
  297.     # potion = max(potions_score, key=lambda p: p[1])[0]
  298.     # potion = random.choice(potions)
  299.     potion = max(potions, key=lambda p: p.price + p.bonus)
  300.     return potion
  301.  
  302.  
  303. add = lambda a, b: [sum(x) for x in list(zip(a, b))]
  304.  
  305. game_turn = 1
  306.  
  307. # game loop
  308. while True:
  309.     action_count = int(input())  # the number of spells and recipes in play
  310.     start: float = time.time()
  311.     idebug(action_count)
  312.     potions: List[Potion] = []
  313.     tome_spells: List[TomeSpell] = []
  314.     witches: List[Witch] = [Witch(i) for i in range(2)]
  315.     me: Witch = witches[0]
  316.     op: Witch = witches[1]
  317.     for i in range(action_count):
  318.         line = input()
  319.         idebug(line)
  320.         inputs = line.split()
  321.         action_id = int(inputs[0])  # the unique ID of this spell or recipe
  322.         action_type = inputs[1]  # in the first league: BREW; later: CAST, OPPONENT_CAST, LEARN, BREW
  323.         delta = [int(inputs[2]), int(inputs[3]), int(inputs[4]), int(inputs[5])]
  324.         price = int(inputs[6])  # the price in rupees if this is a potion
  325.         # in the first two leagues: always 0; later: the index in the tome if this is a tome spell, equal to the read-ahead tax; For brews, this is the value of the current urgency bonus
  326.         tome_index = int(inputs[7])
  327.         # in the first two leagues: always 0; later: the amount of taxed tier-0 ingredients you gain from learning this spell; For brews, this is how many times you can still gain an urgency bonus
  328.         tax_count = int(inputs[8])
  329.         castable = inputs[9] != "0"  # in the first league: always 0; later: 1 if this is a castable player spell
  330.         repeatable = inputs[10] != "0"  # for the first two leagues: always 0; later: 1 if this is a repeatable player spell
  331.         if action_type == 'CAST':
  332.             me.spells.append(Spell(cast_id=action_id, delta=delta, castable=castable, repeatable=repeatable))
  333.         elif action_type == 'OPPONENT_CAST':
  334.             op.spells.append(Spell(cast_id=action_id, delta=delta, castable=castable, repeatable=repeatable))
  335.         elif action_type == 'BREW':
  336.             potions.append(Potion(potion_id=action_id, delta=delta, price=price, bonus=tome_index, bonus_count=tax_count))
  337.         elif action_type == 'LEARN':
  338.             tome_spells.append(TomeSpell(tome_spell_id=action_id, delta=delta, castable=castable, repeatable=repeatable, tome_index=tome_index, tax_count=tax_count))
  339.  
  340.     for i in range(2):
  341.         # inv_0: tier-0 ingredients in inventory
  342.         # score: amount of rupees
  343.         line = input()
  344.         idebug(line)
  345.         inv_0, inv_1, inv_2, inv_3, score = [int(j) for j in line.split()]
  346.         if i == 0:
  347.             me.inv, me.score = [inv_0, inv_1, inv_2, inv_3], score
  348.         else:
  349.             op.inv, op.score = [inv_0, inv_1, inv_2, inv_3], score
  350.  
  351.     debug(f'my spells = {me.spells}')
  352.     debug(f'my inv = {me.inv}')
  353.  
  354.     pycharm_mode = False
  355.  
  356.     if not pycharm_mode and game_turn < 8:
  357.         spell = tome_spells.pop(0)
  358.         action = f'LEARN {spell.id}'
  359.         game_turn += 1
  360.     else:
  361.         # in the first league: BREW <id> | WAIT; later: BREW <id> | CAST <id> [<times>] | LEARN <id> | REST | WAIT
  362.         potion = select_potion_to_brew(potions, me)
  363.         debug(f'potion to brew: {potion} - distance = {me.distance_to(potion)}')
  364.         # debug(f'potion to brew: {potion}')
  365.         order = Order(potion)
  366.  
  367.         # update order status regarding current inventory
  368.         order.update(me.inv)
  369.  
  370.         debug(f'order: {order}')
  371.  
  372.         if not order.is_ready():
  373.             ingredient = order.get_next_ingredient()
  374.             # cast actions to produce recipe
  375.             debug(f'preparing actions_seq for {Color(ingredient)}:')
  376.             actions = order.get_actions_to_produce_ingredient(Color(ingredient), me)
  377.             debug(f'actions = {actions}')
  378.             action = actions[0]
  379.         else:
  380.             action = f'BREW {potion.potion_id}'
  381.  
  382.     print(f'{action} {action}')
  383.  
  384.     debug(f'elapsed time = {round((time.time() - start) * 1000, 2)} ms')
  385.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement