RPG 2: die Schlacht
Kurz gesagt: Eine Schlacht in einem Rollenspiel mit Interfaces für „kann getroffen werden“, „kann sich verteidigen“ und „kann heilen“: ein Kampf auf Leben und Tod und ein Kampf über 10 Züge, in dem der Spieler wählt.
Problem
Gegen wen kämpfen die Helden?
Die Helden aus dem ersten Teil haben schon Leben und Fähigkeiten. Jetzt brauchen sie Feinde: die Monster, die aus den Tiefen von Arkanum aufsteigen. Der Gefallene Schamane ist schwach, der Skelettkönig steckt in einer Panzerung aus Knochen, und der Schlächter wird mit jedem Treffer stärker.
Ein Monster ist kein Held. Es hat keine Fähigkeit, und außerdem kämpfen Helden gegen Monster. Es kann also nicht von Character erben.
Aber der Treffer muss in beide Richtungen funktionieren. Und manche Kämpfer verteidigen sich: Der König blockt einen Teil des Schadens, der Barbar auch. Wenn der Angriff mit einem if für jede Klasse prüft, wer wer ist, braucht jedes neue Monster ein neues if.
Die Idee
Die Monster
Monster haben ihre eigene abstrakte Elternklasse. Sie sieht aus wie Character: ein Name, Leben, Kraft, ein Volk. Ihr Angriff trifft den Helden:
abstract class Monster implements IAttackable { protected String name; protected int health; protected int power; protected String race;
public Monster(String name, int health, int power, String race) { this.name = name; this.health = health; this.power = power; this.race = race; }
public String getName() { return name; }
public int getHealth() { return health; }
@Override public void takeDamage(int amount) { health = Math.max(health - amount, 0); }
public boolean isAlive() { return health > 0; }
public void attack(Character target) { target.defend(power); }}abstract class Monster : IAttackable{ public string Name { get; protected set; } public int Health { get; protected set; } public int Power { get; protected set; } public string Race { get; protected set; }
public Monster(string name, int health, int power, string race) { Name = name; Health = health; Power = power; Race = race; }
public void TakeDamage(int amount) { Health = Math.Max(Health - amount, 0); }
public bool IsAlive() { return Health > 0; }
public virtual void Attack(Character target) { target.Defend(Power); }}class Monster(Attackable): def __init__(self, name, health, power, race): self._name = name self._health = health self._power = power self._race = race
@property def name(self): return self._name
@property def health(self): return self._health
def take_damage(self, amount): self._health = max(self._health - amount, 0)
def is_alive(self): return self._health > 0
def attack(self, target): target.defend(self._power)class Monster { constructor(name, health, power, race) { this._name = name; this._health = health; this._power = power; this._race = race; }
get name() { return this._name; }
get health() { return this._health; }
takeDamage(amount) { this._health = Math.max(this._health - amount, 0); }
isAlive() { return this._health > 0; }
attack(target) { target.defend(this._power); }}Der Gefallene Schamane ist das einfachste Monster: nur Namen und Zahlen. Der Schlächter überschreibt den Angriff: Nach jedem Treffer steigt seine Kraft um 1.
class FallenShaman extends Monster { public FallenShaman() { super("Gefallener Schamane", 60, 12, "Dämon"); }}class FallenShaman : Monster{ public FallenShaman() : base("Gefallener Schamane", 60, 12, "Dämon") { }}class FallenShaman(Monster): def __init__(self): super().__init__("Gefallener Schamane", 60, 12, "Dämon")class FallenShaman extends Monster { constructor() { super("Gefallener Schamane", 60, 12, "Dämon"); }}class Butcher extends Monster { public Butcher() { super("Schlächter", 130, 10, "Dämon"); }
@Override public void attack(Character target) { super.attack(target); power += 1; }}class Butcher : Monster{ public Butcher() : base("Schlächter", 130, 10, "Dämon") { }
public override void Attack(Character target) { base.Attack(target); Power += 1; }}class Butcher(Monster): def __init__(self): super().__init__("Schlächter", 130, 10, "Dämon")
def attack(self, target): super().attack(target) self._power += 1class Butcher extends Monster { constructor() { super("Schlächter", 130, 10, "Dämon"); }
attack(target) { super.attack(target); this._power += 1; }}Die Idee
Drei Verträge
Was Helden und Monster teilen, ist keine Elternklasse, sondern Fähigkeiten. Jede ist ein Interface:
interface IAttackable { void takeDamage(int amount);}
interface IDefendable { void defend(int attackPower);}
interface IHealable { void heal(Character target, int amount);}interface IAttackable{ void TakeDamage(int amount);}
interface IDefendable{ void Defend(int attackPower);}
interface IHealable{ void Heal(Character target, int amount);}from abc import ABC, abstractmethod
class Attackable(ABC): @abstractmethod def take_damage(self, amount): pass
class Defendable(ABC): @abstractmethod def defend(self, attack_power): pass
class Healable(ABC): @abstractmethod def heal(self, target, amount): pass// JavaScript hat keine Interfaces. Der Vertrag ist der Methodenname:// kann getroffen werden: takeDamage(amount)// kann sich verteidigen: defend(attackPower)// kann heilen: heal(target, amount)- IAttackable: kann getroffen werden. Sowohl
Characterals auchMonsterimplementieren es. - IDefendable: kann sich verteidigen. Die Verteidigung entscheidet, wie viel vom Treffer durchkommt. Jeder Held implementiert es über
Character, und unter den Monstern tut das nur der Skelettkönig:
class SkeletonKing extends Monster implements IDefendable { public SkeletonKing() { super("Skelettkönig", 90, 16, "Untoter"); }
@Override public void defend(int attackPower) { takeDamage(Math.max(attackPower - 5, 0)); }}class SkeletonKing : Monster, IDefendable{ public SkeletonKing() : base("Skelettkönig", 90, 16, "Untoter") { }
public void Defend(int attackPower) { TakeDamage(Math.Max(attackPower - 5, 0)); }}class SkeletonKing(Monster, Defendable): def __init__(self): super().__init__("Skelettkönig", 90, 16, "Untoter")
def defend(self, attack_power): self.take_damage(max(attack_power - 5, 0))class SkeletonKing extends Monster { constructor() { super("Skelettkönig", 90, 16, "Untoter"); }
defend(attackPower) { this.takeDamage(Math.max(attackPower - 5, 0)); }}- IHealable: kann heilen, auch sich selbst. Nur einige Helden implementieren es. Der Paladin heilt damit, und der Barbar hat es nicht, aber er überschreibt die Verteidigung: Er erleidet bei jedem Treffer ein Fünftel weniger.
class Paladin extends Character implements IHealable { public Paladin(String name) { super(name, 120, 14, "Mensch"); }
@Override public void heal(Character target, int amount) { target.restore(amount); }
@Override public String useAbility() { heal(this, 20); return name + " heilt sich: +20 Leben"; }}class Paladin : Character, IHealable{ public Paladin(string name) : base(name, 120, 14, "Mensch") { }
public void Heal(Character target, int amount) { target.Restore(amount); }
public override string UseAbility() { Heal(this, 20); return Name + " heilt sich: +20 Leben"; }}class Paladin(Character, Healable): def __init__(self, name): super().__init__(name, 120, 14, "Mensch")
def heal(self, target, amount): target.restore(amount)
def use_ability(self): self.heal(self, 20) return f"{self._name} heilt sich: +20 Leben"class Paladin extends Character { constructor(name) { super(name, 120, 14, "Mensch"); }
heal(target, amount) { target.restore(amount); }
useAbility() { this.heal(this, 20); return `${this._name} heilt sich: +20 Leben`; }}class Barbarian extends Character { public Barbarian(String name) { super(name, 110, 20, "Ork"); }
@Override public void defend(int attackPower) { takeDamage(attackPower - attackPower / 5); }
@Override public String useAbility() { power += power / 2; return name + " gerät in Rage: Kraft " + power; }}class Barbarian : Character{ public Barbarian(string name) : base(name, 110, 20, "Ork") { }
public override void Defend(int attackPower) { TakeDamage(attackPower - attackPower / 5); }
public override string UseAbility() { Power += Power / 2; return Name + " gerät in Rage: Kraft " + Power; }}class Barbarian(Character): def __init__(self, name): super().__init__(name, 110, 20, "Ork")
def defend(self, attack_power): self.take_damage(attack_power - attack_power // 5)
def use_ability(self): self._power += self._power // 2 return f"{self._name} gerät in Rage: Kraft {self._power}"class Barbarian extends Character { constructor(name) { super(name, 110, 20, "Ork"); }
defend(attackPower) { this.takeDamage(attackPower - Math.trunc(attackPower / 5)); }
useAbility() { this._power += Math.trunc(this._power / 2); return `${this._name} gerät in Rage: Kraft ${this._power}`; }}Genauer, für Neugierige
- In C# beginnen Interface-Namen mit
I. In Java ist der übliche Stil ohne dasI:Attackable,Defendable,Healable. Hier sind die Namen in beiden Sprachen gleich, damit du denselben Code liest.
- Im ersten Teil hat der Barbar
takeDamageüberschrieben. Jetzt hat die Verteidigung einen eigenen Namen.takeDamagezieht den Schaden nur ab, unddefendentscheidet, wie viel Schaden durchkommt. So kann es auch Schaden geben, der an der Verteidigung vorbeigeht: Gift, eine Falle. restoresteht inCharacter, damit jeder Held geheilt werden kann. Heilen kann nur, wer IHealable implementiert.
Sieh es dir an
Ein Treffer, zwei Ziele
Hier ist das neue Character. Neben den Verträgen hat es auch einen Angriff:
abstract class Character implements IAttackable, IDefendable { protected String name; protected int health; protected int power; protected String race;
public Character(String name, int health, int power, String race) { this.name = name; this.health = health; this.power = power; this.race = race; }
public String getName() { return name; }
public int getHealth() { return health; }
@Override public void takeDamage(int amount) { health = Math.max(health - amount, 0); }
@Override public void defend(int attackPower) { takeDamage(attackPower); }
public void restore(int amount) { health += amount; }
public boolean isAlive() { return health > 0; }
public void attack(Monster target) { if (target instanceof IDefendable defender) { defender.defend(power); } else { target.takeDamage(power); } }
public abstract String useAbility();}abstract class Character : IAttackable, IDefendable{ public string Name { get; protected set; } public int Health { get; protected set; } public int Power { get; protected set; } public string Race { get; protected set; }
public Character(string name, int health, int power, string race) { Name = name; Health = health; Power = power; Race = race; }
public void TakeDamage(int amount) { Health = Math.Max(Health - amount, 0); }
public virtual void Defend(int attackPower) { TakeDamage(attackPower); }
public void Restore(int amount) { Health += amount; }
public bool IsAlive() { return Health > 0; }
public void Attack(Monster target) { if (target is IDefendable defender) { defender.Defend(Power); } else { target.TakeDamage(Power); } }
public abstract string UseAbility();}class Character(Attackable, Defendable): def __init__(self, name, health, power, race): self._name = name self._health = health self._power = power self._race = race
@property def name(self): return self._name
@property def health(self): return self._health
def take_damage(self, amount): self._health = max(self._health - amount, 0)
def defend(self, attack_power): self.take_damage(attack_power)
def restore(self, amount): self._health += amount
def is_alive(self): return self._health > 0
def attack(self, target): if isinstance(target, Defendable): target.defend(self._power) else: target.take_damage(self._power)
@abstractmethod def use_ability(self): passclass Character { constructor(name, health, power, race) { this._name = name; this._health = health; this._power = power; this._race = race; }
get name() { return this._name; }
get health() { return this._health; }
takeDamage(amount) { this._health = Math.max(this._health - amount, 0); }
defend(attackPower) { this.takeDamage(attackPower); }
restore(amount) { this._health += amount; }
isAlive() { return this._health > 0; }
attack(target) { if (typeof target.defend === "function") { target.defend(this._power); } else { target.takeDamage(this._power); } }}Der Angriff fragt das Ziel: Kannst du dich verteidigen? target instanceof IDefendable defender ist true, wenn das Objekt das Interface implementiert. Dann ist defender dasselbe Objekt, aber als IDefendable, und du kannst darauf defend aufrufen. Sonst wird der Schaden direkt abgezogen.
Grok der Barbar trifft den Schamanen und den König. Dann trifft ihn der Schamane:
Character grok = new Barbarian("Grok");Monster shaman = new FallenShaman();Monster king = new SkeletonKing();grok.attack(shaman);grok.attack(king);System.out.println(shaman.getName() + ": " + shaman.getHealth());System.out.println(king.getName() + ": " + king.getHealth());shaman.attack(grok);System.out.println(grok.getName() + ": " + grok.getHealth());Gefallener Schamane: 40Skelettkönig: 75Grok: 100Character grok = new Barbarian("Grok");Monster shaman = new FallenShaman();Monster king = new SkeletonKing();grok.Attack(shaman);grok.Attack(king);Console.WriteLine(shaman.Name + ": " + shaman.Health);Console.WriteLine(king.Name + ": " + king.Health);shaman.Attack(grok);Console.WriteLine(grok.Name + ": " + grok.Health);Gefallener Schamane: 40Skelettkönig: 75Grok: 100grok = Barbarian("Grok")shaman = FallenShaman()king = SkeletonKing()grok.attack(shaman)grok.attack(king)print(f"{shaman.name}: {shaman.health}")print(f"{king.name}: {king.health}")shaman.attack(grok)print(f"{grok.name}: {grok.health}")Gefallener Schamane: 40Skelettkönig: 75Grok: 100const grok = new Barbarian("Grok");const shaman = new FallenShaman();const king = new SkeletonKing();grok.attack(shaman);grok.attack(king);console.log(`${shaman.name}: ${shaman.health}`);console.log(`${king.name}: ${king.health}`);shaman.attack(grok);console.log(`${grok.name}: ${grok.health}`);Gefallener Schamane: 40Skelettkönig: 75Grok: 100Der Schamane verliert Groks volle Kraft, 20. Der König blockt 5 und verliert 15. Der Schamane trifft mit 12, und Grok erleidet 12 − 2 = 10. Der Angriff weiß nichts über den König oder den Barbaren: Er kennt nur den Vertrag.
Probier es aus
Wer kann heilen?
Was gibt das Programm aus? Die Klassen stammen von den Karten oben.
Character[] party = { new Paladin("Ardan"), new Barbarian("Grok"), new Witch("Morena") };for (Character hero : party) { if (hero instanceof IHealable) { System.out.println(hero.getName()); }}Der Paladin und die Hexe implementieren IHealable, der Barbar nicht. Die Prüfung sieht auf das tatsächliche Objekt, nicht auf den Typ des Arrays.
Sieh es dir an
Ein Kampf auf Leben und Tod
Ardan der Paladin gegen den Gefallenen Schamanen. Jede Runde besteht aus einem Treffer des Helden und, wenn das Monster noch lebt, einem Treffer des Monsters. Der Kampf endet, wenn einer von beiden fällt:
Character hero = new Paladin("Ardan");Monster monster = new FallenShaman();int round = 1;while (hero.isAlive() && monster.isAlive()) { hero.attack(monster); System.out.println(round + ". " + hero.getName() + " trifft: " + monster.getName() + " hat " + monster.getHealth()); if (monster.isAlive()) { monster.attack(hero); System.out.println(" " + monster.getName() + " trifft: " + hero.getName() + " hat " + hero.getHealth()); } round++;}System.out.println(hero.isAlive() ? hero.getName() + " gewinnt" : monster.getName() + " gewinnt");1. Ardan trifft: Gefallener Schamane hat 46 Gefallener Schamane trifft: Ardan hat 1082. Ardan trifft: Gefallener Schamane hat 32 Gefallener Schamane trifft: Ardan hat 963. Ardan trifft: Gefallener Schamane hat 18 Gefallener Schamane trifft: Ardan hat 844. Ardan trifft: Gefallener Schamane hat 4 Gefallener Schamane trifft: Ardan hat 725. Ardan trifft: Gefallener Schamane hat 0Ardan gewinntCharacter hero = new Paladin("Ardan");Monster monster = new FallenShaman();int round = 1;while (hero.IsAlive() && monster.IsAlive()){ hero.Attack(monster); Console.WriteLine(round + ". " + hero.Name + " trifft: " + monster.Name + " hat " + monster.Health); if (monster.IsAlive()) { monster.Attack(hero); Console.WriteLine(" " + monster.Name + " trifft: " + hero.Name + " hat " + hero.Health); } round++;}Console.WriteLine(hero.IsAlive() ? hero.Name + " gewinnt" : monster.Name + " gewinnt");1. Ardan trifft: Gefallener Schamane hat 46 Gefallener Schamane trifft: Ardan hat 1082. Ardan trifft: Gefallener Schamane hat 32 Gefallener Schamane trifft: Ardan hat 963. Ardan trifft: Gefallener Schamane hat 18 Gefallener Schamane trifft: Ardan hat 844. Ardan trifft: Gefallener Schamane hat 4 Gefallener Schamane trifft: Ardan hat 725. Ardan trifft: Gefallener Schamane hat 0Ardan gewinnthero = Paladin("Ardan")monster = FallenShaman()round_number = 1while hero.is_alive() and monster.is_alive(): hero.attack(monster) print(f"{round_number}. {hero.name} trifft: {monster.name} hat {monster.health}") if monster.is_alive(): monster.attack(hero) print(f" {monster.name} trifft: {hero.name} hat {hero.health}") round_number += 1print(f"{hero.name} gewinnt" if hero.is_alive() else f"{monster.name} gewinnt")1. Ardan trifft: Gefallener Schamane hat 46 Gefallener Schamane trifft: Ardan hat 1082. Ardan trifft: Gefallener Schamane hat 32 Gefallener Schamane trifft: Ardan hat 963. Ardan trifft: Gefallener Schamane hat 18 Gefallener Schamane trifft: Ardan hat 844. Ardan trifft: Gefallener Schamane hat 4 Gefallener Schamane trifft: Ardan hat 725. Ardan trifft: Gefallener Schamane hat 0Ardan gewinntconst hero = new Paladin("Ardan");const monster = new FallenShaman();let round = 1;while (hero.isAlive() && monster.isAlive()) { hero.attack(monster); console.log(`${round}. ${hero.name} trifft: ${monster.name} hat ${monster.health}`); if (monster.isAlive()) { monster.attack(hero); console.log(` ${monster.name} trifft: ${hero.name} hat ${hero.health}`); } round++;}console.log(hero.isAlive() ? `${hero.name} gewinnt` : `${monster.name} gewinnt`);1. Ardan trifft: Gefallener Schamane hat 46 Gefallener Schamane trifft: Ardan hat 1082. Ardan trifft: Gefallener Schamane hat 32 Gefallener Schamane trifft: Ardan hat 963. Ardan trifft: Gefallener Schamane hat 18 Gefallener Schamane trifft: Ardan hat 844. Ardan trifft: Gefallener Schamane hat 4 Gefallener Schamane trifft: Ardan hat 725. Ardan trifft: Gefallener Schamane hat 0Ardan gewinntDie Schleife ist die Spielschleife aus der Lektion „Wie du über ein Spiel nachdenkst“: Der Zustand ist das Leben der beiden Kämpfer, die Regeln sind die Angriffe, und das Ende ist die Bedingung des while.
Character hero = new Paladin("Ardan");Monster monster = new FallenShaman();int round = 1;while (hero.isAlive() && monster.isAlive()) { hero.attack(monster); System.out.println(round + ". " + hero.getName() + " trifft: " + monster.getName() + " hat " + monster.getHealth()); if (monster.isAlive()) { monster.attack(hero); System.out.println(" " + monster.getName() + " trifft: " + hero.getName() + " hat " + hero.getHealth()); } round++;}System.out.println(hero.isAlive() ? hero.getName() + " gewinnt" : monster.getName() + " gewinnt");Schritt für Schritt, in Worten
- Ardan beginnt mit 120 Leben, Gefallener Schamane mit 60. Die Runden beginnen bei 1.
- Runde 1: Ardan trifft und nimmt 14 weg. Gefallener Schamane fällt von 60 auf 46.
- Gefallener Schamane lebt und schlägt zurück. Es nimmt 12 weg: Ardan fällt von 120 auf 108.
- Runde 2: Ardan trifft und nimmt 14 weg. Gefallener Schamane fällt von 46 auf 32.
- Gefallener Schamane lebt und schlägt zurück. Es nimmt 12 weg: Ardan fällt von 108 auf 96.
- Runde 3: Ardan trifft und nimmt 14 weg. Gefallener Schamane fällt von 32 auf 18.
- Gefallener Schamane lebt und schlägt zurück. Es nimmt 12 weg: Ardan fällt von 96 auf 84.
- Runde 4: Ardan trifft und nimmt 14 weg. Gefallener Schamane fällt von 18 auf 4.
- Gefallener Schamane lebt und schlägt zurück. Es nimmt 12 weg: Ardan fällt von 84 auf 72.
- Runde 5: Ardan trifft, und Gefallener Schamane fällt von 4 auf 0. monster.isAlive() ist false, also schlägt es nicht zurück.
- Die Bedingung des while ist jetzt false: Einer von beiden lebt nicht mehr. Die Schleife hört auf, und Ardan gewinnt.
Sieh es dir an
10 Züge und die Wahl des Spielers
Gegen den Schlächter reichen Angriffe allein nicht. Deshalb wählt der Spieler in jedem Zug: 1 ist ein Angriff, 2 ist die Fähigkeit des Helden. Der Kampf dauert höchstens 10 Züge, und wenn am Ende beide noch leben, gewinnt der mit mehr Leben:
Scanner in = new Scanner(System.in);Character hero = new Paladin("Ardan");Monster monster = new Butcher();for (int turn = 1; turn <= 10 && hero.isAlive() && monster.isAlive(); turn++) { String choice = in.nextLine(); if (choice.equals("2")) { System.out.println(turn + ". " + hero.useAbility()); } else { hero.attack(monster); System.out.println(turn + ". " + hero.getName() + " trifft: " + monster.getName() + " hat " + monster.getHealth()); } if (monster.isAlive()) { monster.attack(hero); System.out.println(" " + monster.getName() + " trifft: " + hero.getName() + " hat " + hero.getHealth()); }}String winner = hero.getHealth() > monster.getHealth() ? hero.getName() : monster.getName();System.out.println(winner + " gewinnt");1. Ardan trifft: Schlächter hat 116 Schlächter trifft: Ardan hat 1102. Ardan trifft: Schlächter hat 102 Schlächter trifft: Ardan hat 993. Ardan trifft: Schlächter hat 88 Schlächter trifft: Ardan hat 874. Ardan heilt sich: +20 Leben Schlächter trifft: Ardan hat 945. Ardan trifft: Schlächter hat 74 Schlächter trifft: Ardan hat 806. Ardan trifft: Schlächter hat 60 Schlächter trifft: Ardan hat 657. Ardan heilt sich: +20 Leben Schlächter trifft: Ardan hat 698. Ardan trifft: Schlächter hat 46 Schlächter trifft: Ardan hat 529. Ardan heilt sich: +20 Leben Schlächter trifft: Ardan hat 5410. Ardan trifft: Schlächter hat 32 Schlächter trifft: Ardan hat 35Ardan gewinntCharacter hero = new Paladin("Ardan");Monster monster = new Butcher();for (int turn = 1; turn <= 10 && hero.IsAlive() && monster.IsAlive(); turn++){ string choice = Console.ReadLine(); if (choice == "2") { Console.WriteLine(turn + ". " + hero.UseAbility()); } else { hero.Attack(monster); Console.WriteLine(turn + ". " + hero.Name + " trifft: " + monster.Name + " hat " + monster.Health); } if (monster.IsAlive()) { monster.Attack(hero); Console.WriteLine(" " + monster.Name + " trifft: " + hero.Name + " hat " + hero.Health); }}string winner = hero.Health > monster.Health ? hero.Name : monster.Name;Console.WriteLine(winner + " gewinnt");1. Ardan trifft: Schlächter hat 116 Schlächter trifft: Ardan hat 1102. Ardan trifft: Schlächter hat 102 Schlächter trifft: Ardan hat 993. Ardan trifft: Schlächter hat 88 Schlächter trifft: Ardan hat 874. Ardan heilt sich: +20 Leben Schlächter trifft: Ardan hat 945. Ardan trifft: Schlächter hat 74 Schlächter trifft: Ardan hat 806. Ardan trifft: Schlächter hat 60 Schlächter trifft: Ardan hat 657. Ardan heilt sich: +20 Leben Schlächter trifft: Ardan hat 698. Ardan trifft: Schlächter hat 46 Schlächter trifft: Ardan hat 529. Ardan heilt sich: +20 Leben Schlächter trifft: Ardan hat 5410. Ardan trifft: Schlächter hat 32 Schlächter trifft: Ardan hat 35Ardan gewinnthero = Paladin("Ardan")monster = Butcher()turn = 1while turn <= 10 and hero.is_alive() and monster.is_alive(): choice = input() if choice == "2": print(f"{turn}. {hero.use_ability()}") else: hero.attack(monster) print(f"{turn}. {hero.name} trifft: {monster.name} hat {monster.health}") if monster.is_alive(): monster.attack(hero) print(f" {monster.name} trifft: {hero.name} hat {hero.health}") turn += 1winner = hero.name if hero.health > monster.health else monster.nameprint(f"{winner} gewinnt")1. Ardan trifft: Schlächter hat 116 Schlächter trifft: Ardan hat 1102. Ardan trifft: Schlächter hat 102 Schlächter trifft: Ardan hat 993. Ardan trifft: Schlächter hat 88 Schlächter trifft: Ardan hat 874. Ardan heilt sich: +20 Leben Schlächter trifft: Ardan hat 945. Ardan trifft: Schlächter hat 74 Schlächter trifft: Ardan hat 806. Ardan trifft: Schlächter hat 60 Schlächter trifft: Ardan hat 657. Ardan heilt sich: +20 Leben Schlächter trifft: Ardan hat 698. Ardan trifft: Schlächter hat 46 Schlächter trifft: Ardan hat 529. Ardan heilt sich: +20 Leben Schlächter trifft: Ardan hat 5410. Ardan trifft: Schlächter hat 32 Schlächter trifft: Ardan hat 35Ardan gewinntconst hero = new Paladin("Ardan");const monster = new Butcher();for (let turn = 1; turn <= 10 && hero.isAlive() && monster.isAlive(); turn++) { const choice = await ask(""); if (choice === "2") { console.log(`${turn}. ${hero.useAbility()}`); } else { hero.attack(monster); console.log(`${turn}. ${hero.name} trifft: ${monster.name} hat ${monster.health}`); } if (monster.isAlive()) { monster.attack(hero); console.log(` ${monster.name} trifft: ${hero.name} hat ${hero.health}`); }}const winner = hero.health > monster.health ? hero.name : monster.name;console.log(`${winner} gewinnt`);rl.close();1. Ardan trifft: Schlächter hat 116 Schlächter trifft: Ardan hat 1102. Ardan trifft: Schlächter hat 102 Schlächter trifft: Ardan hat 993. Ardan trifft: Schlächter hat 88 Schlächter trifft: Ardan hat 874. Ardan heilt sich: +20 Leben Schlächter trifft: Ardan hat 945. Ardan trifft: Schlächter hat 74 Schlächter trifft: Ardan hat 806. Ardan trifft: Schlächter hat 60 Schlächter trifft: Ardan hat 657. Ardan heilt sich: +20 Leben Schlächter trifft: Ardan hat 698. Ardan trifft: Schlächter hat 46 Schlächter trifft: Ardan hat 529. Ardan heilt sich: +20 Leben Schlächter trifft: Ardan hat 5410. Ardan trifft: Schlächter hat 32 Schlächter trifft: Ardan hat 35Ardan gewinntDie Eingabe für dieses Beispiel ist:
1 1 1 2 1 1 2 1 2 1Wenn der Spieler nur Angriffe wählt, zehnmal 1, fällt der Paladin in Zug 9, und der Schlächter hat noch 4 Leben. Mit drei Heilungen reichen die Züge bis zum Ende, und der Paladin gewinnt mit 35 Leben gegen 32.
Genauer, für Neugierige
- Die Wahl wird in jedem Zug gelesen, aber wenn der Held fällt, hört die Schleife auf, und die übrigen Eingabezeilen werden nicht gelesen.
choice.equals("2")vergleicht den Text. Alles andere, auch eine leere Zeile, ist ein Angriff: Das Spiel darf bei einer unbekannten Wahl nicht stehen bleiben. Wie du eine falsche Wahl mit einer Exception ablehnst, siehst du im dritten Teil.
Probier es aus
Finde den Fehler
Der Barbar trifft den Skelettkönig mit Kraft 20, und der König verliert 20 statt 15. In welcher Zeile des Angriffs steckt der Fehler?
Wähle die Zeile mit dem Fehler.
Die Prüfung stellt fest, dass der König sich verteidigen kann, aber dann wird der Schaden direkt abgezogen und defend umgangen. Beide Zweige tun dasselbe. Du brauchst defender.defend(power);.
Probier es aus
Was fehlt?
public void attack(Monster target) {
if (target IDefendable defender) {
defender.defend(power);
} else {
target.takeDamage(power);
}
}instanceof prüft das tatsächliche Objekt. Mit einem Namen hinter dem Typ, defender, stehen Prüfung und Umwandlung in einer Zeile.Übung
Mehr Schlachten
Löse diese Aufgaben in deinem Editor. Beginne mit den Klassen aus der Lektion.
1. Ein neues Monster. Andariel, eine Dämonin, 100 Leben und 12 Kraft. Ihr Treffer ist vergiftet: Nach dem normalen Angriff erleidet das Ziel direkt über takeDamage noch 4 Schaden, an der Verteidigung vorbei. Lass den Kampf auf Leben und Tod mit Grok dem Barbaren laufen.
1. Grok trifft: Andariel hat 80 Andariel trifft: Grok hat 962. Grok trifft: Andariel hat 60 Andariel trifft: Grok hat 823. Grok trifft: Andariel hat 40 Andariel trifft: Grok hat 684. Grok trifft: Andariel hat 20 Andariel trifft: Grok hat 545. Grok trifft: Andariel hat 0Grok gewinntGrok erleidet 10 durch den Treffer (12 minus ein Fünftel) und 4 durch das Gift: 14 pro Runde.
2. Ein Heiltrank. Eine Klasse Potion mit einer Menge Leben. use(Character target) heilt das Ziel und gibt einen Text zurück. Der Trank kann einmal getrunken werden: Danach sagt er „Der Trank ist leer“. Im Kampf des Paladins gegen den Schlächter trinkt der Held einen Trank über 40, statt zuzuschlagen, wenn sein Leben unter 30 liegt.
1. Ardan trifft: Schlächter hat 116 Schlächter trifft: Ardan hat 1102. Ardan trifft: Schlächter hat 102 Schlächter trifft: Ardan hat 993. Ardan trifft: Schlächter hat 88 Schlächter trifft: Ardan hat 874. Ardan trifft: Schlächter hat 74 Schlächter trifft: Ardan hat 745. Ardan trifft: Schlächter hat 60 Schlächter trifft: Ardan hat 606. Ardan trifft: Schlächter hat 46 Schlächter trifft: Ardan hat 457. Ardan trifft: Schlächter hat 32 Schlächter trifft: Ardan hat 298. Ardan trinkt einen Trank: +40 Leben Schlächter trifft: Ardan hat 529. Ardan trifft: Schlächter hat 18 Schlächter trifft: Ardan hat 3410. Ardan trifft: Schlächter hat 4 Schlächter trifft: Ardan hat 1511. Ardan trifft: Schlächter hat 0Ardan gewinnt3. Ein Kampfbericht. Statt einer Zeile für jeden Treffer gib am Ende aus, wie viele Runden der Kampf gedauert hat, wie viel Schaden der Held verursacht und wie viel er erlitten hat und wie viel Leben ihm bleibt. Grok der Barbar gegen den Skelettkönig:
Runden: 6Schaden verursacht: 90Schaden erlitten: 65Leben übrig: 45Tipp: Merke dir das Leben des Ziels vor dem Treffer und ziehe sein Leben nach dem Treffer ab.
Weitere Übungen
- Hexe gegen König. Lass den Kampf von Morena der Hexe gegen den Skelettkönig laufen, mit den Wahlen aus der Eingabe. Kann sie in 10 Zügen gewinnen?
- Heiler der Gruppe. Der Paladin heilt in einem Array aus drei Helden den mit dem wenigsten Leben um 10, statt sich selbst.
- Wer schlägt zuerst? Entscheide mit
new Random(5)zu Beginn jeder Runde, wer zuerst zuschlägt.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Polymorphie · Abstrakte Klassen · Interfaces · Typprüfungen
- Aufgaben zum Thema: Wir bauen ein Spiel