#!/usr/bin/env python3 import time import random import sys import subprocess class Unit: def __init__(self): self.title = 'Default Unit' self.hp = 100 self.hp_max = 100 self.damage_min = 10 self.damage_max = 15 self.effects = [] self.abilities = [] def add_effect(self, effect): if self._can_effect_be_added(effect): self.effects.append( {'effect': effect, 'lifetime': 0} ) def add_damage(self, damage): self.hp -= damage if self.hp < 0: self.hp = 0 def remove_effect_set(self, effect): self.effects.remove(effect) def _can_effect_be_added(self, effect): count = 0 for e in self.effects: if e['effect'].id == effect.id: count += 1 if count > effect.stags_max: return False return True class Mage(Unit): def __init__(self): Unit.__init__(self) self.mana = 100 self.mana_max = 100 class Player(Mage): def __init__(self): Mage.__init__(self) self.target = None self.mana = 400 self.mana_max = 400 class Ability: def __init__(self, caster): self.caster = caster self.title = 'Default Ability' self.mana_cost = 0 self.error_message = '' def cast(self) -> bool: raise Exception('Cast method must be implemented!') class DaggerImpact(Ability): def __init__(self, caster): Ability.__init__(self, caster) self.title = 'Klingenstoß' self.mana_cost = 150 self.damage = 60 def cast(self) -> bool: if self.caster.target is None: self.error_message = 'Kein Ziel ausgewählt!' return False self.caster.target.hp -= self.damage self.caster.target.add_effect(EffectBleedOut()) return True class Ui: def show_enemies(self, enemies): raise Exception('Method must be implemented!') class Cli(Ui): def __init__(self): Ui.__init__(self) def clear_screen(self): if sys.platform == 'win32': subprocess.call(['cls']) else: subprocess.call(['clear']) def show_enemies(self, player, enemies): print() print('Vor dir stehen:') for e in enemies: name = '\t%s (%d/%d)' % (e.title, e.hp, e.hp_max) if e == player.target: name = ' >' + name print(name) def show_message_board(self, *lines): ornament = '-=W=-' width = len(ornament) + 2 for l in lines: length = len(l) if length > width: width = length if width % 2 == 0: width += 1 side = int((width - len(ornament) + 2) / 2) print() print('o' + ('-' * side) + ornament + ('-' * side) + 'o') for l in lines: print('| ' + l.ljust(width, ' ') + ' |') print('o' + ('-' * (width + 2)) + 'o') def show_player_status(self, player): self.show_message_board( 'HP: %d/%d Mana: %d/%d' % (player.hp, player.hp_max, player.mana, player.mana_max) ) def make_choice(self, choices = [], callbacks = []): if len(choices) != len(callbacks): raise Exception('Number of callbacks must equal the number of choices!') print() for i,c in enumerate(choices): print('[%d] %s' % (i + 1, c)) print() choice = 0 while choice == 0: choice = int(input('Triff deine Wahl: ')) if choice < 1 or choice > len(choices): choice = 0 else: callbacks[choice - 1]() return class Effect: def __init__(self): self.id = 0 self.title = 'Effect' self.stags_max = 1 self.lifetime = 0 def apply_to_unit(self, unit) -> str: raise Exception('This abstract method must be implemented!') return 'This abstract method must be implemented!' class EffectBleedOut(Effect): def __init__(self): Effect.__init__(self) self.id = 1 self.title = 'Blutung' self.amount = 3 self.lifetime = 5 self.stags_max = 5 def apply_to_unit(self, unit): unit.hp -= self.amount class Fight: def __init__(self, ui, player): self.ui = ui self.player = player self.enemies = [] self.is_running = True def add_enemy(self, enemy): self.enemies.append(enemy) def start(self): while len(self.enemies) > 0: self.ui.clear_screen() if not self.is_running: print('Feigling!') return self.ui.show_player_status(self.player) self.ui.show_enemies(self.player, self.enemies) choices = [] callbacks = [] choices.append('Gegner wählen') callbacks.append(self.choose_enemy) if self.player.target is not None: choices.append('Angreifen') callbacks.append(self.attack_target) choices.append('Fähigkeit wirken') callbacks.append(self.choose_ability) choices.append('Weglaufen') callbacks.append(self.quit) ui.make_choice(choices, callbacks) print('Du warst siegreich.') def enemies_attack(self): for e in self.enemies: damage = random.randint(e.damage_min, e.damage_max) self.player.add_damage(damage) print('%s greift an und verursacht %d Schaden.' % (e.title, damage)) def quit(self): self.is_running = False def attack_target(self): damage = random.randint(self.player.damage_min, self.player.damage_max) self.player.target.add_damage(damage) print('Du hast %d Schaden an %s verursacht.' % (damage, self.player.target.title)) def choose_enemy(self): choices = [] callbacks = [] for e in self.enemies: choices.append('%s (%d/%d)' % (e.title, e.hp, e.hp_max)) callbacks.append(lambda e=e: self.set_player_target(e)) ui.make_choice(choices, callbacks) def set_player_target(self, target): self.player.target = target print('%s ist jetzt dein Ziel.' % (target.title)) def choose_ability(self): names = [] callbacks = [] for a in self.player.abilities: names.append('%s (%d Mana)' % (a.title, a.mana_cost)) callbacks.append(lambda a=a: self.cast_ability(a)) self.ui.make_choice(names, callbacks) def cast_ability(self, ability): if ability.mana_cost > self.player.mana: print('Nicht genug Mana!') return ability.cast() self.player.mana -= ability.mana_cost print('Wirke %s.' % (ability.title)) if __name__ == '__main__': ui = Cli() player = Player() player.abilities = [ DaggerImpact(player) ] dog = Unit() dog.title = 'Räudiger Hund' dog.hp = 20 dog.hp_max = 20 dragon = Unit() dragon.title = 'Feuerdrache' dragon.hp = 800 dragon.hp_max = 800 orc = Unit() orc.title = 'Dämlicher Ork' orc.hp = 350 orc.hp_max = 350 fight = Fight(ui, player) fight.add_enemy(dog) fight.add_enemy(dragon) fight.add_enemy(orc) fight.start() u = Unit() u.add_effect(EffectBleedOut()) while u.hp > 0: to_be_removed = [] for e in u.effects: e['effect'].apply_to_unit(u) e['lifetime'] += 1 if e['lifetime'] == e['effect'].lifetime: to_be_removed.append(e) for r in to_be_removed: print(r['effect'].title, 'klingt ab.') u.remove_effect_set(r) if u.hp < 0: u.hp = 0 print(u.hp, end='') time.sleep(1)