Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import math
- import random
- import sys
- import time
- from copy import copy
- from typing import Tuple, List, Optional
- # from collections import deque
- # from numpy import array, zeros
- from enum import Enum
- class Color(Enum):
- BLUE = 0
- GREEN = 1
- ORANGE = 2
- YELLOW = 3
- def idebug(*args):
- return
- print(*args, file=sys.stderr, flush=True)
- def debug(*args):
- # return
- print(*args, file=sys.stderr, flush=True)
- class TomeSpell:
- def __init__(self, tome_spell_id: int, delta: List[int], castable: int, repeatable: int, tome_index: int, tax_count: int):
- self.tome_spell_id = tome_spell_id
- self.delta = delta
- self.castable = castable
- self.repeatable = repeatable
- self.tome_index = tome_index
- self.tax_count = tax_count
- @property
- def id(self):
- return self.tome_spell_id
- def __copy__(self):
- return Spell(self.tome_spell_id, self.delta.copy(), self.castable, self.repeatable)
- def __hash__(self):
- return hash((str(self.delta), self.castable, self.repeatable))
- def __repr__(self):
- # return f'{self.cast_id} {str(self.delta)} {self.castable}'
- return f'{self.tome_spell_id} {self.castable} {self.repeatable} {self.tome_index} {self.tax_count}'
- class Spell:
- def __init__(self, cast_id: int, delta: List[int], castable: int, repeatable: int):
- self.cast_id = cast_id
- self.delta = delta
- self.castable = castable
- self.repeatable = repeatable
- @property
- def id(self):
- return self.cast_id
- def __copy__(self):
- return Spell(self.cast_id, self.delta.copy(), self.castable, self.repeatable)
- def __hash__(self):
- return hash((str(self.delta), self.castable, self.repeatable))
- def __repr__(self):
- return f'{self.cast_id} {str(self.delta)} {self.castable}'
- # return f'{self.cast_id} {self.castable}'
- class Potion:
- def __init__(self, potion_id: int, delta: List[int], price: int, bonus: int, bonus_count: int):
- self.potion_id = potion_id
- self.delta = delta
- self.price = price
- self.bonus = bonus
- self.bonus_count = bonus_count
- @property
- def id(self):
- return self.potion_id
- def __repr__(self):
- return f'{self.potion_id} {str(self.delta)} {self.price + self.bonus}'
- class Witch:
- def __init__(self, witch_id: int):
- self.witch_id = witch_id
- self.inv: List[int] = []
- self.spells: List[Spell] = []
- self.score = 0
- @property
- def id(self) -> int:
- return self.witch_id
- def rest(self) -> None:
- for s in self.spells:
- s.castable = True
- def distance_to(self, p: Potion) -> int:
- """
- Distance to potion p (Calculate the number of actions needed to deliver potion p)
- :param p: potion to deliver
- :return: number of actions
- """
- order: Order = Order(p)
- actions: List[Action] = []
- recipe = {Color(i): 0 for i in range(4)}
- while not order.is_ready():
- ingredient: int = order.get_next_ingredient()
- # cast actions to produce ingredient
- needed_actions: List[Action] = order.get_actions_to_produce_ingredient(ingredient=Color(ingredient), witch=self)
- # debug(f'preparing {len(actions_recipe)} actions for {Color(recipe)}: {actions_recipe}')
- order.ingredients[ingredient] += 1
- actions += needed_actions
- recipe[Color(ingredient)] += 1
- debug(f'{len(actions)} actions needed to produce {recipe} = \n {actions}')
- return len(actions)
- def __copy__(self):
- return Witch(self.witch_id, self.inv[:], [copy(s) for s in self.spells])
- def __repr__(self):
- return f'{self.witch_id} {self.inv} {self.spells}'
- class Recipe:
- def __init(self, color: Color):
- self.color = color
- class Order:
- def __init__(self, p: Potion):
- self.potion = p
- self.ingredients: List[int] = [0] * 4
- def update(self, inv: List[int]) -> None:
- ingredient: int = self.get_next_ingredient()
- inventory: List[int] = inv[:]
- while ingredient is not None:
- # debug(f'recipe color needed for order : {Color(recipe)}')
- if inventory[ingredient] == 0:
- # debug(f'missing {Color(recipe)} recipe')
- break
- self.ingredients[ingredient] += 1
- inventory[ingredient] -= 1
- ingredient = self.get_next_ingredient()
- def get_next_ingredient(self) -> int:
- """ingredient to produce"""
- ingredient_request: int = [i for i, ing_count in enumerate(self.potion.delta) if -ing_count > self.ingredients[i]]
- return max(ingredient_request) if ingredient_request else None
- def is_ready(self) -> bool:
- return sum(add(self.potion.delta, self.ingredients)) == 0
- def get_actions_recipe_staging(self, recipe: Color, witch: Witch, actions=[]):
- # List of spells that can produce recipe
- available_spells = [s for s in witch.spells if s.delta[recipe.value] > 0 if s.castable]
- if not available_spells:
- action = Action('REST')
- witch.rest()
- self.get_actions_to_produce_ingredient(recipe, copy(witch), actions + [action])
- else:
- 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]
- for s in spell_candidates:
- debug(f'recipe {recipe} can be produced by spell {s.id}')
- # needed_recipes = [(s, Color(r_id), count) for r_id, count in enumerate(s.delta) for s in available_spells if count < 0][0]
- cheapest_spell = min(spell_candidates, key=lambda x: x[1])[0]
- debug(f'recipe {recipe} best spell {cheapest_spell.id}')
- action = Action('CAST', cheapest_spell.id)
- self.get_actions_to_produce_ingredient(recipe, copy(witch), actions + [action])
- def get_actions_to_produce_ingredient(self, ingredient: Color, witch: Witch):
- """
- :param inv: ingredient
- :param spells:
- :return: list of cast actions needed to prepare recipe
- """
- actions = []
- spells = [copy(s) for s in witch.spells]
- # cinv = {Color.BLUE: inv[0], Color.GREEN: inv[1], Color.ORANGE: inv[2], Color.YELLOW: inv[3]}
- # Spell needed to produce recipe
- first_spell = [s for s in spells if s.delta[ingredient.value] > 0][0]
- if ingredient == Color.BLUE:
- if not first_spell.castable:
- actions.append('REST')
- actions.append(Action('CAST', first_spell.id))
- return actions
- else:
- # recipe(s) needed to cast spell
- first_ingredient = [Color(r_id) for r_id, count in enumerate(first_spell.delta) if count < 0][0]
- # If we can get a recipe_id from inventory (not yet reserved for the potion)
- if witch.inv[first_ingredient.value] > self.ingredients[first_ingredient.value]:
- if not first_spell.castable:
- actions.append('REST')
- actions.append(Action('CAST', first_spell.id))
- return actions
- else:
- # we need to cast a second spell to produce this recipe
- second_spell = [s for s in spells if s.delta[first_ingredient.value] > 0][0]
- if first_ingredient == Color.BLUE:
- if not (first_spell.castable and second_spell.castable):
- actions.append('REST')
- actions.append(Action('CAST', second_spell.id))
- actions.append(Action('CAST', first_spell.id))
- return actions
- else:
- second_ingredient = [Color(r_id) for r_id, count in enumerate(second_spell.delta) if count < 0][0]
- # If we can get a recipe_id not reserved for the potion
- if witch.inv[second_ingredient.value] > self.ingredients[second_ingredient.value]:
- if not (second_spell.castable and first_spell.castable):
- actions.append('REST')
- actions.append(Action('CAST', second_spell.id))
- actions.append(Action('CAST', first_spell.id))
- return actions
- else:
- third_spell = [s for s in spells if s.delta[second_ingredient.value] > 0][0]
- if second_ingredient == Color.BLUE:
- # recipe(s) needed to cast spell
- if not (first_spell.castable and second_spell.castable and third_spell.castable):
- actions.append('REST')
- actions.append(Action('CAST', third_spell.id))
- actions.append(Action('CAST', second_spell.id))
- actions.append(Action('CAST', first_spell.id))
- return actions
- else:
- third_ingredient = [Color(r_id) for r_id, count in enumerate(third_spell.delta) if count < 0][0]
- # If we can get a recipe_id not reserved for the potion
- if witch.inv[third_ingredient.value] > self.ingredients[third_ingredient.value]:
- if not (first_spell.castable and second_spell.castable and third_spell.castable):
- actions.append('REST')
- actions.append(Action('CAST', third_spell.id))
- actions.append(Action('CAST', second_spell.id))
- actions.append(Action('CAST', first_spell.id))
- return actions
- else:
- fourth_spell = [s for s in spells if s.delta[third_ingredient.value] > 0][0]
- # recipe(s) needed to cast spell
- if not (first_spell.castable and second_spell.castable and third_spell.castable and fourth_spell.castable):
- actions.append('REST')
- actions.append(Action('CAST', fourth_spell.id))
- actions.append(Action('CAST', third_spell.id))
- actions.append(Action('CAST', second_spell.id))
- actions.append(Action('CAST', first_spell.id))
- return actions
- def __repr__(self):
- if not self.is_ready():
- blue = f'blue: {self.ingredients[0]}/{-self.potion.delta[0]}'
- green = f'green: {self.ingredients[1]}/{-self.potion.delta[1]}'
- orange = f'orange: {self.ingredients[2]}/{-self.potion.delta[2]}'
- yellow = f'yellow: {self.ingredients[3]}/{-self.potion.delta[3]}'
- return f'{blue} - {green} - {orange} - {yellow}'
- return f'order {self.potion} READY!'
- class Action:
- def __init__(self, action_type, action_id=None):
- self.action_type = action_type
- self.action_id = action_id
- @property
- def id(self):
- return self.action_id
- def __hash__(self):
- return hash((self.action_type, self.action_id))
- def __repr__(self):
- if self.action_type == 'CAST':
- return f'CAST {self.action_id}'
- elif self.action_type == 'BREW':
- return f'BREW {self.action_id}'
- else:
- return f'REST'
- def select_potion_to_brew(potions: List[Potion], witch: Witch) -> Potion:
- potions_score = []
- # op_witch: Witch = players[0 if witch.witch_id else 1]
- # for p in potions:
- # my_score = (p.price + p.bonus) / (1 + distance_to(p, witch))
- # op_score = (p.price + p.bonus) / (1 + distance_to(p, op_witch))
- # # potions_score.append((p, my_score / (1 + op_score)))
- # potions_score.append((p, my_score))
- # # potions_score.sort(key=lambda p: p[1], reverse=True)
- # for p, score in potions_score:
- # debug(f'potion {p} - score = {round(score, 2)}')
- # potion = max(potions_score, key=lambda p: p[1])[0]
- # potion = random.choice(potions)
- potion = max(potions, key=lambda p: p.price + p.bonus)
- return potion
- add = lambda a, b: [sum(x) for x in list(zip(a, b))]
- game_turn = 1
- # game loop
- while True:
- action_count = int(input()) # the number of spells and recipes in play
- start: float = time.time()
- idebug(action_count)
- potions: List[Potion] = []
- tome_spells: List[TomeSpell] = []
- witches: List[Witch] = [Witch(i) for i in range(2)]
- me: Witch = witches[0]
- op: Witch = witches[1]
- for i in range(action_count):
- line = input()
- idebug(line)
- inputs = line.split()
- action_id = int(inputs[0]) # the unique ID of this spell or recipe
- action_type = inputs[1] # in the first league: BREW; later: CAST, OPPONENT_CAST, LEARN, BREW
- delta = [int(inputs[2]), int(inputs[3]), int(inputs[4]), int(inputs[5])]
- price = int(inputs[6]) # the price in rupees if this is a potion
- # 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
- tome_index = int(inputs[7])
- # 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
- tax_count = int(inputs[8])
- castable = inputs[9] != "0" # in the first league: always 0; later: 1 if this is a castable player spell
- repeatable = inputs[10] != "0" # for the first two leagues: always 0; later: 1 if this is a repeatable player spell
- if action_type == 'CAST':
- me.spells.append(Spell(cast_id=action_id, delta=delta, castable=castable, repeatable=repeatable))
- elif action_type == 'OPPONENT_CAST':
- op.spells.append(Spell(cast_id=action_id, delta=delta, castable=castable, repeatable=repeatable))
- elif action_type == 'BREW':
- potions.append(Potion(potion_id=action_id, delta=delta, price=price, bonus=tome_index, bonus_count=tax_count))
- elif action_type == 'LEARN':
- tome_spells.append(TomeSpell(tome_spell_id=action_id, delta=delta, castable=castable, repeatable=repeatable, tome_index=tome_index, tax_count=tax_count))
- for i in range(2):
- # inv_0: tier-0 ingredients in inventory
- # score: amount of rupees
- line = input()
- idebug(line)
- inv_0, inv_1, inv_2, inv_3, score = [int(j) for j in line.split()]
- if i == 0:
- me.inv, me.score = [inv_0, inv_1, inv_2, inv_3], score
- else:
- op.inv, op.score = [inv_0, inv_1, inv_2, inv_3], score
- debug(f'my spells = {me.spells}')
- debug(f'my inv = {me.inv}')
- pycharm_mode = False
- if not pycharm_mode and game_turn < 8:
- spell = tome_spells.pop(0)
- action = f'LEARN {spell.id}'
- game_turn += 1
- else:
- # in the first league: BREW <id> | WAIT; later: BREW <id> | CAST <id> [<times>] | LEARN <id> | REST | WAIT
- potion = select_potion_to_brew(potions, me)
- debug(f'potion to brew: {potion} - distance = {me.distance_to(potion)}')
- # debug(f'potion to brew: {potion}')
- order = Order(potion)
- # update order status regarding current inventory
- order.update(me.inv)
- debug(f'order: {order}')
- if not order.is_ready():
- ingredient = order.get_next_ingredient()
- # cast actions to produce recipe
- debug(f'preparing actions_seq for {Color(ingredient)}:')
- actions = order.get_actions_to_produce_ingredient(Color(ingredient), me)
- debug(f'actions = {actions}')
- action = actions[0]
- else:
- action = f'BREW {potion.potion_id}'
- print(f'{action} {action}')
- debug(f'elapsed time = {round((time.time() - start) * 1000, 2)} ms')
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement