n | from typing import Any | n | |
| import math | | import math |
| import copy | | import copy |
| from collections import OrderedDict | | from collections import OrderedDict |
| | | |
| | | |
| class Potion: | | class Potion: |
n | potions_used_in_reactions = set() | n | |
| | | |
| def __init__(self, effects, duration): | | def __init__(self, effects, duration): |
| self.effects = {effect: [func, 1] for effect, func in effects.items()} | | self.effects = {effect: [func, 1] for effect, func in effects.items()} |
| self.duration = duration | | self.duration = duration |
n | | n | self.used_in_reation = False |
| | | self.depleted = False |
| | | |
| def __getattr__(self, attribute_name): | | def __getattr__(self, attribute_name): |
| if ( | | if ( |
| attribute_name in self.effects | | attribute_name in self.effects |
| and self.effects[attribute_name][0] is not None | | and self.effects[attribute_name][0] is not None |
| ): | | ): |
| return lambda target: self._apply_effect( | | return lambda target: self._apply_effect( |
| target, attribute_name, self.effects[attribute_name][0] | | target, attribute_name, self.effects[attribute_name][0] |
| ) | | ) |
n | elif Potion._check_if_depleted(self): | n | elif self.depleted == True: |
| raise TypeError("Potion is depleted.") | | raise TypeError("Potion is depleted.") |
| elif attribute_name in self.effects and self.effects[attribute_name][0] is None: | | elif attribute_name in self.effects and self.effects[attribute_name][0] is None: |
| raise TypeError("Effect is depleted.") | | raise TypeError("Effect is depleted.") |
| else: | | else: |
| raise AttributeError(f"Object doesn't have attribute {attribute_name}.") | | raise AttributeError(f"Object doesn't have attribute {attribute_name}.") |
| | | |
n | @staticmethod | n | |
| def _is_used_in_reation(potion): | | |
| if not isinstance(potion, Potion): | | |
| raise TypeError("Object must be of type Potion.") | | |
| return id(potion) in Potion.potions_used_in_reactions | | |
| | | |
| @staticmethod | | |
| def _check_if_depleted(potion): | | |
| if not isinstance(potion, Potion): | | |
| raise TypeError("Object must be of type Potion.") | | |
| return all( | | |
| potion.effects[effect_name][0] is None | | |
| for effect_name, (_, _) in potion.effects.items() | | |
| ) | | |
| | | |
| def _potion_validation_check(func): | | def _potion_validation_check(func): |
| def wrapper(*args, **kwargs): | | def wrapper(*args, **kwargs): |
n | if Potion._is_used_in_reation(args[0]): | n | if args[0].used_in_reation == True: |
| raise TypeError("Potion is now part of something bigger than itself.") | | raise TypeError("Potion is now part of something bigger than itself.") |
n | elif isinstance(args[1], Potion) and Potion._is_used_in_reation(args[1]): | n | elif isinstance(args[1], Potion) and args[1].used_in_reation == True: |
| raise TypeError("Potion is now part of something bigger than itself.") | | raise TypeError("Potion is now part of something bigger than itself.") |
n | elif Potion._check_if_depleted(args[0]): | n | elif args[0].depleted == True: |
| raise TypeError("Potion is depleted.") | | raise TypeError("Potion is depleted.") |
n | elif isinstance(args[1], Potion) and Potion._check_if_depleted(args[1]): | n | elif isinstance(args[1], Potion) and args[1].depleted == True: |
| raise TypeError("Potion is depleted.") | | raise TypeError("Potion is depleted.") |
| return func(*args, **kwargs) | | return func(*args, **kwargs) |
| | | |
| return wrapper | | return wrapper |
| | | |
| @_potion_validation_check | | @_potion_validation_check |
| def _apply_effect(self, target, effect_name, effect): | | def _apply_effect(self, target, effect_name, effect): |
| for _ in range(self.effects[effect_name][1]): | | for _ in range(self.effects[effect_name][1]): |
| effect(target) | | effect(target) |
n | self.effects[effect_name][0] = None # we mark the used effect function as None | n | self.effects[effect_name][0] = None |
| | | self.effects[effect_name][1] = 0 |
| | | # we mark the used effect function as None |
| | | # and intesity as none |
| | | |
| @_potion_validation_check | | @_potion_validation_check |
| def __add__(self, other): | | def __add__(self, other): |
| if not isinstance(other, Potion): | | if not isinstance(other, Potion): |
| raise TypeError("Addition is only supported between Potion instances.") | | raise TypeError("Addition is only supported between Potion instances.") |
| combined_effects = self.effects | | combined_effects = self.effects |
| | | |
| for effect_name, effect_intensity in other.effects.items(): | | for effect_name, effect_intensity in other.effects.items(): |
| if effect_name not in combined_effects: | | if effect_name not in combined_effects: |
| combined_effects[effect_name] = effect_intensity | | combined_effects[effect_name] = effect_intensity |
| else: | | else: |
| if ( | | if ( |
| combined_effects[effect_name][0] is None | | combined_effects[effect_name][0] is None |
| and effect_intensity[0] is None | | and effect_intensity[0] is None |
| ): | | ): |
| combined_effects[effect_name] = [ | | combined_effects[effect_name] = [ |
| None, | | None, |
| combined_effects[effect_name][1] + effect_intensity[1], | | combined_effects[effect_name][1] + effect_intensity[1], |
| ] | | ] |
| elif ( | | elif ( |
| combined_effects[effect_name][0] is not None | | combined_effects[effect_name][0] is not None |
| and effect_intensity[0] is None | | and effect_intensity[0] is None |
| ): | | ): |
| pass | | pass |
| elif ( | | elif ( |
| combined_effects[effect_name][0] is None | | combined_effects[effect_name][0] is None |
| and effect_intensity[0] is not None | | and effect_intensity[0] is not None |
| ): | | ): |
| combined_effects[effect_name] = [ | | combined_effects[effect_name] = [ |
| effect_intensity[0], | | effect_intensity[0], |
| effect_intensity[1], | | effect_intensity[1], |
| ] | | ] |
| else: | | else: |
| combined_effects[effect_name][1] = ( | | combined_effects[effect_name][1] = ( |
| combined_effects[effect_name][1] + effect_intensity[1] | | combined_effects[effect_name][1] + effect_intensity[1] |
| ) | | ) |
| | | |
| combined_duration = max(self.duration, other.duration) | | combined_duration = max(self.duration, other.duration) |
| combined_potion = Potion({}, combined_duration) | | combined_potion = Potion({}, combined_duration) |
| combined_potion.effects = combined_effects | | combined_potion.effects = combined_effects |
n | Potion.potions_used_in_reactions.add(id(self)) | n | self.used_in_reation = True |
| Potion.potions_used_in_reactions.add(id(other)) | | other.used_in_reation = True |
| return combined_potion | | return combined_potion |
| | | |
| @_potion_validation_check | | @_potion_validation_check |
| def multiplication(self, factor): | | def multiplication(self, factor): |
| effects_multiplied_intesities = { | | effects_multiplied_intesities = { |
| effect: [func_intensity[0], func_intensity[1] * factor] | | effect: [func_intensity[0], func_intensity[1] * factor] |
| for effect, func_intensity in self.effects.items() | | for effect, func_intensity in self.effects.items() |
| } | | } |
| multiplied_potion = Potion({}, self.duration) | | multiplied_potion = Potion({}, self.duration) |
| multiplied_potion.effects = effects_multiplied_intesities | | multiplied_potion.effects = effects_multiplied_intesities |
n | Potion.potions_used_in_reactions.add(id(self)) | n | self.used_in_reation = True |
| return multiplied_potion | | return multiplied_potion |
| | | |
| @staticmethod | | @staticmethod |
| def round_float(float_number): | | def round_float(float_number): |
| if isinstance(float_number, float): | | if isinstance(float_number, float): |
| if float_number - math.floor(float_number) <= 0.5: | | if float_number - math.floor(float_number) <= 0.5: |
| return math.floor(float_number) | | return math.floor(float_number) |
| else: | | else: |
| return math.ceil(float_number) | | return math.ceil(float_number) |
| else: | | else: |
| raise TypeError("This function works only with float numbers.") | | raise TypeError("This function works only with float numbers.") |
| | | |
| @_potion_validation_check | | @_potion_validation_check |
| def delution(self, factor): | | def delution(self, factor): |
| diluted_effects = { | | diluted_effects = { |
| effect_name: [ | | effect_name: [ |
| effect_intensity[0], | | effect_intensity[0], |
| Potion.round_float(effect_intensity[1] * factor), | | Potion.round_float(effect_intensity[1] * factor), |
| ] | | ] |
| for effect_name, effect_intensity in self.effects.items() | | for effect_name, effect_intensity in self.effects.items() |
| } | | } |
| diluted_potion = Potion({}, self.duration) | | diluted_potion = Potion({}, self.duration) |
| diluted_potion.effects = diluted_effects | | diluted_potion.effects = diluted_effects |
n | Potion.potions_used_in_reactions.add(id(self)) | n | self.used_in_reation = True |
| return diluted_potion | | return diluted_potion |
| | | |
| @_potion_validation_check | | @_potion_validation_check |
| def __sub__(self, other): | | def __sub__(self, other): |
| if not isinstance(other, Potion): | | if not isinstance(other, Potion): |
| raise TypeError("Subtraction is only supported between Potion instances.") | | raise TypeError("Subtraction is only supported between Potion instances.") |
| | | |
| if any(effect_name not in self.effects for effect_name in other.effects): | | if any(effect_name not in self.effects for effect_name in other.effects): |
| raise TypeError( | | raise TypeError( |
| "The right-hand side Potion has effects not present in the left-hand side Potion." | | "The right-hand side Potion has effects not present in the left-hand side Potion." |
| ) | | ) |
| | | |
| subtracted_effects = self.effects | | subtracted_effects = self.effects |
| for effect_name, effect_intesity in other.effects.items(): | | for effect_name, effect_intesity in other.effects.items(): |
| updated_intesity = subtracted_effects[effect_name][1] - effect_intesity[1] | | updated_intesity = subtracted_effects[effect_name][1] - effect_intesity[1] |
| if updated_intesity <= 0: | | if updated_intesity <= 0: |
| del subtracted_effects[effect_name] | | del subtracted_effects[effect_name] |
| else: | | else: |
| subtracted_effects[effect_name][1] = updated_intesity | | subtracted_effects[effect_name][1] = updated_intesity |
| | | |
| subtracted_duration = ( | | subtracted_duration = ( |
| self.duration | | self.duration |
| ) # Duration remains the same as the left-hand side Potion | | ) # Duration remains the same as the left-hand side Potion |
| | | |
| subtracted_potion = Potion({}, subtracted_duration) | | subtracted_potion = Potion({}, subtracted_duration) |
| subtracted_potion.effects = subtracted_effects | | subtracted_potion.effects = subtracted_effects |
n | Potion.potions_used_in_reactions.add(id(self)) | n | self.used_in_reation = True |
| Potion.potions_used_in_reactions.add(id(other)) | | other.used_in_reation = True |
| return subtracted_potion | | return subtracted_potion |
| | | |
| def __mul__(self, factor): | | def __mul__(self, factor): |
| if factor < 0: | | if factor < 0: |
| raise TypeError("Invalid parameter.") | | raise TypeError("Invalid parameter.") |
| elif isinstance(factor, float) and factor > 0 and factor < 1: | | elif isinstance(factor, float) and factor > 0 and factor < 1: |
| return self.delution(factor) | | return self.delution(factor) |
| elif isinstance(factor, int): | | elif isinstance(factor, int): |
| return self.multiplication(factor) | | return self.multiplication(factor) |
| else: | | else: |
| raise TypeError("Invalid parameter.") | | raise TypeError("Invalid parameter.") |
| | | |
| @_potion_validation_check | | @_potion_validation_check |
| def __truediv__(self, number): | | def __truediv__(self, number): |
| if not isinstance(number, int): | | if not isinstance(number, int): |
| raise TypeError("You can divide the Potion only with whole number.") | | raise TypeError("You can divide the Potion only with whole number.") |
| divided_effects = { | | divided_effects = { |
| effect_name: [ | | effect_name: [ |
| effect_intesity[0], | | effect_intesity[0], |
| Potion.round_float(effect_intesity[1] / number), | | Potion.round_float(effect_intesity[1] / number), |
| ] | | ] |
| for effect_name, effect_intesity in self.effects.items() | | for effect_name, effect_intesity in self.effects.items() |
| } | | } |
| new_potions = [] | | new_potions = [] |
| for _ in range(number): | | for _ in range(number): |
| potion = Potion({}, self.duration) | | potion = Potion({}, self.duration) |
| potion.effects = copy.deepcopy(divided_effects) | | potion.effects = copy.deepcopy(divided_effects) |
| new_potions.append(potion) | | new_potions.append(potion) |
| | | |
n | Potion.potions_used_in_reactions.add(id(self)) | n | self.used_in_reation = True |
| return tuple(new_potions) | | return tuple(new_potions) |
| | | |
| @property | | @property |
| def _total_intensity(self): | | def _total_intensity(self): |
| return sum( | | return sum( |
| intensity | | intensity |
| for _, (func, intensity) in self.effects.items() | | for _, (func, intensity) in self.effects.items() |
| if func is not None | | if func is not None |
| ) | | ) |
| | | |
| @_potion_validation_check | | @_potion_validation_check |
| def __lt__(self, other): | | def __lt__(self, other): |
| if not isinstance(other, Potion): | | if not isinstance(other, Potion): |
| raise TypeError("Comparison is only supported between Potion instances.") | | raise TypeError("Comparison is only supported between Potion instances.") |
| return self._total_intensity < other._total_intensity | | return self._total_intensity < other._total_intensity |
| | | |
| @_potion_validation_check | | @_potion_validation_check |
| def __eq__(self, other): | | def __eq__(self, other): |
| if not isinstance(other, Potion): | | if not isinstance(other, Potion): |
| return False | | return False |
| return all( | | return all( |
| effect_name in self.effects | | effect_name in self.effects |
| and effect_intensity[1] == self.effects[effect_name][1] | | and effect_intensity[1] == self.effects[effect_name][1] |
| for effect_name, effect_intensity in other.effects.items() | | for effect_name, effect_intensity in other.effects.items() |
| ) | | ) |
| | | |
| def __deepcopy__(self, memo): | | def __deepcopy__(self, memo): |
| new_potion = type(self).__new__(type(self)) | | new_potion = type(self).__new__(type(self)) |
| memo[id(self)] = new_potion | | memo[id(self)] = new_potion |
| new_potion.__dict__.update(copy.deepcopy(self.__dict__, memo)) | | new_potion.__dict__.update(copy.deepcopy(self.__dict__, memo)) |
| return new_potion | | return new_potion |
n | | n | |
| | | def __dir__(self): |
| | | return self.effects |
| | | |
| | | |
| class ГоспожатаПоХимия: | | class ГоспожатаПоХимия: |
| def __init__(self): | | def __init__(self): |
| self.targets = {} | | self.targets = {} |
| | | |
| # calculate the molecular mass of every effect | | # calculate the molecular mass of every effect |
| def molecular_mass(self, effect_name): | | def molecular_mass(self, effect_name): |
| if not isinstance(effect_name, str): | | if not isinstance(effect_name, str): |
| raise TypeError("Funtion works only with string.") | | raise TypeError("Funtion works only with string.") |
| return sum(ord(char) for char in effect_name) | | return sum(ord(char) for char in effect_name) |
| | | |
| # how the target will be if havent taken anything | | # how the target will be if havent taken anything |
| def original_state(self, target): | | def original_state(self, target): |
| return copy.deepcopy(target.__dict__) | | return copy.deepcopy(target.__dict__) |
| | | |
| # Restore the sober state of the target | | # Restore the sober state of the target |
| def restore_original_state(self, target_id): | | def restore_original_state(self, target_id): |
| target_dict_copy = copy.deepcopy(self.targets[target_id][1]) | | target_dict_copy = copy.deepcopy(self.targets[target_id][1]) |
| self.targets[target_id][0].__dict__ = target_dict_copy | | self.targets[target_id][0].__dict__ = target_dict_copy |
| | | |
| # Keep an record for every potion that the target is ON at that moment | | # Keep an record for every potion that the target is ON at that moment |
| def poitions_in_the_blood(self, target, potion): | | def poitions_in_the_blood(self, target, potion): |
| if id(target) not in self.targets: | | if id(target) not in self.targets: |
| self.targets[id(target)] = [ | | self.targets[id(target)] = [ |
| target, | | target, |
| self.original_state(target), | | self.original_state(target), |
| OrderedDict(), | | OrderedDict(), |
| ] | | ] |
| self.targets[id(target)][2][id(potion)] = copy.deepcopy(potion) | | self.targets[id(target)][2][id(potion)] = copy.deepcopy(potion) |
| else: | | else: |
| self.targets[id(target)][2][id(potion)] = copy.deepcopy(potion) | | self.targets[id(target)][2][id(potion)] = copy.deepcopy(potion) |
| | | |
| # special apply by the teacher :) | | # special apply by the teacher :) |
| def apply_potions(self, target_id): | | def apply_potions(self, target_id): |
| potions_applied_copy = copy.deepcopy( | | potions_applied_copy = copy.deepcopy( |
| list(self.targets[target_id][2].values()) | | list(self.targets[target_id][2].values()) |
| ) # Use deepcopy | | ) # Use deepcopy |
| target = self.targets[target_id][0] | | target = self.targets[target_id][0] |
| for applied_potion in potions_applied_copy: | | for applied_potion in potions_applied_copy: |
| sorted_effects = sorted( | | sorted_effects = sorted( |
| applied_potion.effects.keys(), key=self.molecular_mass, reverse=True | | applied_potion.effects.keys(), key=self.molecular_mass, reverse=True |
| ) | | ) |
| for effect_name in sorted_effects: | | for effect_name in sorted_effects: |
| if applied_potion.effects[effect_name][0] is not None: | | if applied_potion.effects[effect_name][0] is not None: |
| getattr(applied_potion, effect_name)(target) | | getattr(applied_potion, effect_name)(target) |
| | | |
| def update_state(self, targets_to_update): | | def update_state(self, targets_to_update): |
| for target_id in targets_to_update: | | for target_id in targets_to_update: |
| self.restore_original_state(target_id) | | self.restore_original_state(target_id) |
| self.apply_potions(target_id) | | self.apply_potions(target_id) |
| | | |
| # tick-tac | | # tick-tac |
| def tick(self): | | def tick(self): |
| targets_to_update = [] | | targets_to_update = [] |
| for target_id, [ | | for target_id, [ |
| target, | | target, |
| target_original_state, | | target_original_state, |
| potions_applied, | | potions_applied, |
| ] in self.targets.items(): | | ] in self.targets.items(): |
| potions_to_remove = [] | | potions_to_remove = [] |
| for potion_id, potion in list(potions_applied.items()): | | for potion_id, potion in list(potions_applied.items()): |
| potion.duration -= 1 | | potion.duration -= 1 |
| if potion.duration <= 0: | | if potion.duration <= 0: |
| potions_to_remove.append(potion_id) | | potions_to_remove.append(potion_id) |
| targets_to_update.append(target_id) | | targets_to_update.append(target_id) |
| for potion_id in potions_to_remove: | | for potion_id in potions_to_remove: |
| del potions_applied[potion_id] | | del potions_applied[potion_id] |
| self.update_state(targets_to_update) | | self.update_state(targets_to_update) |
| | | |
| def apply(self, target, potion): | | def apply(self, target, potion): |
| if not isinstance(potion, Potion): | | if not isinstance(potion, Potion): |
| raise TypeError("Object must be of type Potion to be applied.") | | raise TypeError("Object must be of type Potion to be applied.") |
n | if Potion._check_if_depleted(potion): | n | if potion.depleted: |
| raise TypeError("Potion is depleted.") | | raise TypeError("Potion is depleted.") |
n | if Potion._is_used_in_reation(potion): | n | if potion.used_in_reation: |
| raise TypeError("Potion is now part of something bigger than itself.") | | raise TypeError("Potion is now part of something bigger than itself.") |
| if potion.duration > 0: | | if potion.duration > 0: |
| self.poitions_in_the_blood(target, potion) | | self.poitions_in_the_blood(target, potion) |
| sorted_effects = sorted( | | sorted_effects = sorted( |
| potion.effects.keys(), key=self.molecular_mass, reverse=True | | potion.effects.keys(), key=self.molecular_mass, reverse=True |
| ) | | ) |
| for effect_name in sorted_effects: | | for effect_name in sorted_effects: |
| if potion.effects[effect_name][0] is not None: | | if potion.effects[effect_name][0] is not None: |
| getattr(potion, effect_name)(target) | | getattr(potion, effect_name)(target) |
t | | t | potion.depleted = True |