def multiple_calls_wrapper(func, count): def wrapper(*args, **kwargs): for _ in range(count): func(*args, **kwargs) return wrapper custom_round = lambda x: int(x) + 1 if x % 1 > 0.5 else int(x) class Potion: def __init__(self, effects, duration): self.effects = effects self.intensities = {effect : 1 for effect in self.effects.keys()} self.depleted_effects = set() self.is_depleted = False self.is_applied = False self.duration = duration def __getattr__(self, name): if self.is_depleted: raise TypeError("Potion is now part of something bigger than itself.") elif self.is_applied or name in self.effects and name in self.depleted_effects: raise TypeError("Effect is depleted.") elif name in self.effects and name not in self.depleted_effects: self.depleted_effects.add(name) self.is_applied = self.depleted_effects == set(self.effects) return multiple_calls_wrapper(self.effects[name], self.intensities[name]) else: raise AttributeError(f"Couldn't find {name} attribute") def __add__(self, other_potion): if self.is_depleted or other_potion.is_depleted: raise TypeError("Potion is now part of something bigger than itself.") if self.is_applied or other_potion.is_applied: raise TypeError("Potion is depleted.") self.is_depleted = True other_potion.is_depleted = True new_duration = max(self.duration, other_potion.duration) new_effects = {**self.effects, **other_potion.effects} #union of intensities, where intensities are summed for common effects new_intensities = {key : self.intensities.get(key) for key in set(self.intensities.keys()) - self.depleted_effects} for key in set(other_potion.intensities.keys()) - other_potion.depleted_effects: if key in new_intensities: new_intensities[key] += other_potion.intensities[key] else: new_intensities[key] = other_potion.intensities[key] result = Potion(new_effects, new_duration) result.intensities = new_intensities return result def __mul__(self, times): if self.is_depleted: raise TypeError("Potion is now part of something bigger than itself.") if self.is_applied: raise TypeError("Potion is depleted.") self.is_depleted = True #dict comprehension with custom rounding criteria #(>x.5 is rounded to x + 1; <=x.5 is rounded to x) new_intensities = {key: custom_round(times * val) for (key, val) in self.intensities.items()} result = Potion(self.effects, self.duration) result.intensities = new_intensities result.depleted_effects = self.depleted_effects return result def __rmul__(self, times): return self.__mul__(times) def __imul__(self, times): self = self.__mul__(times) return self def __sub__(self, other_potion): if self.is_depleted or other_potion.is_depleted: raise TypeError("Potion is now part of something bigger than itself.") if self.is_applied or other_potion.is_applied: raise TypeError("Potion is depleted.") self.is_depleted = True other_potion.is_depleted = True if not (set(other_potion.effects.keys()) <= set(self.effects.keys())): raise TypeError("Invalid subtract") new_intensities = self.intensities for effect in other_potion.intensities: new_intensities[effect] -= other_potion.intensities[effect] if new_intensities[effect] < 0: del new_intensities[effect] new_effects = {key : val for (key, val) in self.effects.items() if key in new_intensities} result = Potion(new_effects, self.duration) result.intensities = new_intensities result.depleted_effects = self.depleted_effects & set(new_effects.keys()) return result def __truediv__(self, times): if self.is_depleted: raise TypeError("Potion is now part of something bigger than itself.") if self.is_applied: raise TypeError("Potion is depleted.") self.is_depleted = True new_intensities = {key : custom_round(val / times) for (key, val) in self.intensities.items()} result = Potion(self.effects, self.duration) result.intensities = new_intensities return tuple([result for _ in range(times)]) def __eq__(self, other_potion): first_effects = set(self.intensities) - set(self.depleted_effects) sec - set(other_potion.depleted_effects) if first_effects != second_effects: return False for effect in first_effects: if other_potion.intensities[effect] != self.intensities[effect]: return False return True def __gt__(self, other_potion): first_sum = sum([self.intensities[effect] for effect in self.effects if effect not in self.depleted_effects]) sec for effect in other_potion.effects if effect not in other_potion.depleted_effects]) return first_sum > second_sum class ГоспожатаПоХимия: def __init__(self): pass #history = {} def apply(self, target, potion): if potion.is_applied: raise TypeError("Potion is depleted.") if potion.is_depleted: raise TypeError("Potion is now part of something bigger than itself.") # if not target in self.history.keys(): # self.history[target] = [target.__dict__.copy()] #self.history[target].extend([potion.__dict__.copy(), potion.duration]) inorder_effects = [effect for effect in potion.effects.keys() if not effect in potion.depleted_effects] inorder_effects.sort(key = lambda x : sum([ord(ch) for ch in x]), reverse = True) for effect in inorder_effects: potion.__getattr__(effect)(target) potion.is_applied = True def tick(self): pass #for key, val in self.history.items(): #testing: class Target: def __init__(self): self.size = 5 target = Target() effects = {'grow': lambda target: setattr(target, 'size', target.size*2)} grow_potion=Potion(effects,duration=2) def elemental_dmg_immunity(target): target.fire_dmg_resistance = 1.0 # Percentage value between 0 and 1 target.water_dmg_resistance = 1.0 target.earth_dmg_resistance = 1.0 target.air_dmg_resistance = 1.0 def physical_dmg_immunity(target): target.bludgeoning_dmg_resistance = 1.0 target.slashing_dmg_resistance = 1.0 target.piercing_dmg_resistance = 1.0 immunity_potion = Potion({'make_elemental_immune': elemental_dmg_immunity, 'make_physical_immune': physical_dmg_immunity}, duration=1) dimitrichka = ГоспожатаПоХимия() # grow_potion и immunity_potion са отварите от горния пример grow_and_immunity_potion = grow_potion + immunity_potion # target.size = 5 print(target.__dict__) print('----------------------------') dimitrichka.apply(target, grow_and_immunity_potion) print(target.__dict__) # target.size = 10 # target.*_resistance = 1.0 (* за да не ги изброяваме)