RPG 2: the battle
Problem
Who do the heroes fight?
The heroes from the first part already have health and abilities. Now they need enemies: the monsters rising from the depths of Arcanum. The Fallen Shaman is weak, the Skeleton King is covered in bone armor, and the Butcher gets stronger with every hit.
A monster is not a hero. It has no ability, and besides, heroes fight against monsters. So it cannot inherit from Character.
But the hit has to work in both directions. And some of the fighters defend themselves: the king blocks part of the damage, and so does the barbarian. If the attack checks who is who with an if for every class, every new monster will need a new if.
The idea
The monsters
Monsters have their own abstract parent. It looks like Character: a name, health, power, a race. Its attack hits the hero:
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); }}The Fallen Shaman is the simplest monster: only names and numbers. The Butcher overrides the attack: after every hit his power goes up by 1.
class FallenShaman extends Monster { public FallenShaman() { super("Fallen Shaman", 60, 12, "demon"); }}class FallenShaman : Monster{ public FallenShaman() : base("Fallen Shaman", 60, 12, "demon") { }}class FallenShaman(Monster): def __init__(self): super().__init__("Fallen Shaman", 60, 12, "demon")class FallenShaman extends Monster { constructor() { super("Fallen Shaman", 60, 12, "demon"); }}class Butcher extends Monster { public Butcher() { super("Butcher", 130, 10, "demon"); }
@Override public void attack(Character target) { super.attack(target); power += 1; }}class Butcher : Monster{ public Butcher() : base("Butcher", 130, 10, "demon") { }
public override void Attack(Character target) { base.Attack(target); Power += 1; }}class Butcher(Monster): def __init__(self): super().__init__("Butcher", 130, 10, "demon")
def attack(self, target): super().attack(target) self._power += 1class Butcher extends Monster { constructor() { super("Butcher", 130, 10, "demon"); }
attack(target) { super.attack(target); this._power += 1; }}The idea
Three contracts
What heroes and monsters share is not a parent but abilities. Each one is an 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 has no interfaces. The contract is the method name:// can be hit: takeDamage(amount)// can defend: defend(attackPower)// can heal: heal(target, amount)- IAttackable: can be hit. Both
CharacterandMonsterimplement it. - IDefendable: can defend. The defense decides how much of the hit gets through. Every hero implements it through
Character, and among the monsters only the Skeleton King does:
class SkeletonKing extends Monster implements IDefendable { public SkeletonKing() { super("Skeleton King", 90, 16, "undead"); }
@Override public void defend(int attackPower) { takeDamage(Math.max(attackPower - 5, 0)); }}class SkeletonKing : Monster, IDefendable{ public SkeletonKing() : base("Skeleton King", 90, 16, "undead") { }
public void Defend(int attackPower) { TakeDamage(Math.Max(attackPower - 5, 0)); }}class SkeletonKing(Monster, Defendable): def __init__(self): super().__init__("Skeleton King", 90, 16, "undead")
def defend(self, attack_power): self.take_damage(max(attack_power - 5, 0))class SkeletonKing extends Monster { constructor() { super("Skeleton King", 90, 16, "undead"); }
defend(attackPower) { this.takeDamage(Math.max(attackPower - 5, 0)); }}- IHealable: can heal, including itself. Only some heroes implement it. The paladin heals with it, and the barbarian does not have it, but he overrides the defense: he takes one fifth less from every hit.
class Paladin extends Character implements IHealable { public Paladin(String name) { super(name, 120, 14, "human"); }
@Override public void heal(Character target, int amount) { target.restore(amount); }
@Override public String useAbility() { heal(this, 20); return name + " heals: +20 health"; }}class Paladin : Character, IHealable{ public Paladin(string name) : base(name, 120, 14, "human") { }
public void Heal(Character target, int amount) { target.Restore(amount); }
public override string UseAbility() { Heal(this, 20); return Name + " heals: +20 health"; }}class Paladin(Character, Healable): def __init__(self, name): super().__init__(name, 120, 14, "human")
def heal(self, target, amount): target.restore(amount)
def use_ability(self): self.heal(self, 20) return f"{self._name} heals: +20 health"class Paladin extends Character { constructor(name) { super(name, 120, 14, "human"); }
heal(target, amount) { target.restore(amount); }
useAbility() { this.heal(this, 20); return `${this._name} heals: +20 health`; }}class Barbarian extends Character { public Barbarian(String name) { super(name, 110, 20, "orc"); }
@Override public void defend(int attackPower) { takeDamage(attackPower - attackPower / 5); }
@Override public String useAbility() { power += power / 2; return name + " flies into a rage: power " + power; }}class Barbarian : Character{ public Barbarian(string name) : base(name, 110, 20, "orc") { }
public override void Defend(int attackPower) { TakeDamage(attackPower - attackPower / 5); }
public override string UseAbility() { Power += Power / 2; return Name + " flies into a rage: power " + Power; }}class Barbarian(Character): def __init__(self, name): super().__init__(name, 110, 20, "orc")
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} flies into a rage: power {self._power}"class Barbarian extends Character { constructor(name) { super(name, 110, 20, "orc"); }
defend(attackPower) { this.takeDamage(attackPower - Math.trunc(attackPower / 5)); }
useAbility() { this._power += Math.trunc(this._power / 2); return `${this._name} flies into a rage: power ${this._power}`; }}More precisely, if you are curious
- In C# interface names start with
I. In Java the usual style is without theI:Attackable,Defendable,Healable. Here the names are the same in both languages, so you read the same code.
- In the first part, the barbarian overrode
takeDamage. Now the defense has its own name.takeDamageonly subtracts the damage, anddefenddecides how much damage gets through. That way there can also be damage that goes around the defense: poison, a trap. restoreis inCharacter, so that every hero can be healed. Only someone who implements IHealable can do the healing.
See it
One hit, two targets
Here is the new Character. Besides the contracts, it also has an attack:
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); } }}The attack asks the target: can you defend? target instanceof IDefendable defender is true if the object implements the interface. Then defender is the same object, but as an IDefendable, and you can call defend on it. Otherwise the damage is subtracted directly.
Grok the barbarian hits the shaman and the king. Then the shaman hits him:
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());Fallen Shaman: 40Skeleton King: 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);Fallen Shaman: 40Skeleton King: 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}")Fallen Shaman: 40Skeleton King: 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}`);Fallen Shaman: 40Skeleton King: 75Grok: 100The shaman loses Grok's full power, 20. The king blocks 5 and loses 15. The shaman hits with 12, and Grok takes 12 − 2 = 10. The attack knows nothing about the king or the barbarian: it only knows the contract.
Try it
Who can heal?
What will the program print? The classes are from the cards above.
Character[] party = { new Paladin("Ardan"), new Barbarian("Grok"), new Witch("Morena") };for (Character hero : party) { if (hero instanceof IHealable) { System.out.println(hero.getName()); }}The paladin and the witch implement IHealable, and the barbarian does not. The check looks at the actual object, not at the type of the array.
See it
A fight to the death
Ardan the paladin against the Fallen Shaman. Each round is a hit by the hero and, if the monster is still alive, a hit by the monster. The fight ends when one of them falls:
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() + " hits: " + monster.getName() + " has " + monster.getHealth()); if (monster.isAlive()) { monster.attack(hero); System.out.println(" " + monster.getName() + " hits: " + hero.getName() + " has " + hero.getHealth()); } round++;}System.out.println(hero.isAlive() ? hero.getName() + " wins" : monster.getName() + " wins");1. Ardan hits: Fallen Shaman has 46 Fallen Shaman hits: Ardan has 1082. Ardan hits: Fallen Shaman has 32 Fallen Shaman hits: Ardan has 963. Ardan hits: Fallen Shaman has 18 Fallen Shaman hits: Ardan has 844. Ardan hits: Fallen Shaman has 4 Fallen Shaman hits: Ardan has 725. Ardan hits: Fallen Shaman has 0Ardan winsCharacter hero = new Paladin("Ardan");Monster monster = new FallenShaman();int round = 1;while (hero.IsAlive() && monster.IsAlive()){ hero.Attack(monster); Console.WriteLine(round + ". " + hero.Name + " hits: " + monster.Name + " has " + monster.Health); if (monster.IsAlive()) { monster.Attack(hero); Console.WriteLine(" " + monster.Name + " hits: " + hero.Name + " has " + hero.Health); } round++;}Console.WriteLine(hero.IsAlive() ? hero.Name + " wins" : monster.Name + " wins");1. Ardan hits: Fallen Shaman has 46 Fallen Shaman hits: Ardan has 1082. Ardan hits: Fallen Shaman has 32 Fallen Shaman hits: Ardan has 963. Ardan hits: Fallen Shaman has 18 Fallen Shaman hits: Ardan has 844. Ardan hits: Fallen Shaman has 4 Fallen Shaman hits: Ardan has 725. Ardan hits: Fallen Shaman has 0Ardan winshero = Paladin("Ardan")monster = FallenShaman()round_number = 1while hero.is_alive() and monster.is_alive(): hero.attack(monster) print(f"{round_number}. {hero.name} hits: {monster.name} has {monster.health}") if monster.is_alive(): monster.attack(hero) print(f" {monster.name} hits: {hero.name} has {hero.health}") round_number += 1print(f"{hero.name} wins" if hero.is_alive() else f"{monster.name} wins")1. Ardan hits: Fallen Shaman has 46 Fallen Shaman hits: Ardan has 1082. Ardan hits: Fallen Shaman has 32 Fallen Shaman hits: Ardan has 963. Ardan hits: Fallen Shaman has 18 Fallen Shaman hits: Ardan has 844. Ardan hits: Fallen Shaman has 4 Fallen Shaman hits: Ardan has 725. Ardan hits: Fallen Shaman has 0Ardan winsconst hero = new Paladin("Ardan");const monster = new FallenShaman();let round = 1;while (hero.isAlive() && monster.isAlive()) { hero.attack(monster); console.log(`${round}. ${hero.name} hits: ${monster.name} has ${monster.health}`); if (monster.isAlive()) { monster.attack(hero); console.log(` ${monster.name} hits: ${hero.name} has ${hero.health}`); } round++;}console.log(hero.isAlive() ? `${hero.name} wins` : `${monster.name} wins`);1. Ardan hits: Fallen Shaman has 46 Fallen Shaman hits: Ardan has 1082. Ardan hits: Fallen Shaman has 32 Fallen Shaman hits: Ardan has 963. Ardan hits: Fallen Shaman has 18 Fallen Shaman hits: Ardan has 844. Ardan hits: Fallen Shaman has 4 Fallen Shaman hits: Ardan has 725. Ardan hits: Fallen Shaman has 0Ardan winsThe loop is the game loop from the lesson "How to think about a game": the state is the health of the two fighters, the rules are the attacks, and the end is the condition of the 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() + " hits: " + monster.getName() + " has " + monster.getHealth()); if (monster.isAlive()) { monster.attack(hero); System.out.println(" " + monster.getName() + " hits: " + hero.getName() + " has " + hero.getHealth()); } round++;}System.out.println(hero.isAlive() ? hero.getName() + " wins" : monster.getName() + " wins");Step by step, in words
- Ardan starts with 120 health, Fallen Shaman with 60. The rounds start at 1.
- Round 1: Ardan hits and takes away 14. Fallen Shaman drops from 60 to 46.
- Fallen Shaman is alive and strikes back. It takes away 12: Ardan drops from 120 to 108.
- Round 2: Ardan hits and takes away 14. Fallen Shaman drops from 46 to 32.
- Fallen Shaman is alive and strikes back. It takes away 12: Ardan drops from 108 to 96.
- Round 3: Ardan hits and takes away 14. Fallen Shaman drops from 32 to 18.
- Fallen Shaman is alive and strikes back. It takes away 12: Ardan drops from 96 to 84.
- Round 4: Ardan hits and takes away 14. Fallen Shaman drops from 18 to 4.
- Fallen Shaman is alive and strikes back. It takes away 12: Ardan drops from 84 to 72.
- Round 5: Ardan hits and Fallen Shaman drops from 4 to 0. monster.isAlive() is false, so it does not strike back.
- The condition of the while is now false: one of them is not alive. The loop stops, and Ardan wins.
See it
10 turns and the player's choice
Against the Butcher, attacks alone are not enough. So the player chooses every turn: 1 is an attack, 2 is the hero's ability. The fight is at most 10 turns, and if both are still alive at the end, the healthier one wins:
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() + " hits: " + monster.getName() + " has " + monster.getHealth()); } if (monster.isAlive()) { monster.attack(hero); System.out.println(" " + monster.getName() + " hits: " + hero.getName() + " has " + hero.getHealth()); }}String winner = hero.getHealth() > monster.getHealth() ? hero.getName() : monster.getName();System.out.println(winner + " wins");1. Ardan hits: Butcher has 116 Butcher hits: Ardan has 1102. Ardan hits: Butcher has 102 Butcher hits: Ardan has 993. Ardan hits: Butcher has 88 Butcher hits: Ardan has 874. Ardan heals: +20 health Butcher hits: Ardan has 945. Ardan hits: Butcher has 74 Butcher hits: Ardan has 806. Ardan hits: Butcher has 60 Butcher hits: Ardan has 657. Ardan heals: +20 health Butcher hits: Ardan has 698. Ardan hits: Butcher has 46 Butcher hits: Ardan has 529. Ardan heals: +20 health Butcher hits: Ardan has 5410. Ardan hits: Butcher has 32 Butcher hits: Ardan has 35Ardan winsCharacter 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 + " hits: " + monster.Name + " has " + monster.Health); } if (monster.IsAlive()) { monster.Attack(hero); Console.WriteLine(" " + monster.Name + " hits: " + hero.Name + " has " + hero.Health); }}string winner = hero.Health > monster.Health ? hero.Name : monster.Name;Console.WriteLine(winner + " wins");1. Ardan hits: Butcher has 116 Butcher hits: Ardan has 1102. Ardan hits: Butcher has 102 Butcher hits: Ardan has 993. Ardan hits: Butcher has 88 Butcher hits: Ardan has 874. Ardan heals: +20 health Butcher hits: Ardan has 945. Ardan hits: Butcher has 74 Butcher hits: Ardan has 806. Ardan hits: Butcher has 60 Butcher hits: Ardan has 657. Ardan heals: +20 health Butcher hits: Ardan has 698. Ardan hits: Butcher has 46 Butcher hits: Ardan has 529. Ardan heals: +20 health Butcher hits: Ardan has 5410. Ardan hits: Butcher has 32 Butcher hits: Ardan has 35Ardan winshero = 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} hits: {monster.name} has {monster.health}") if monster.is_alive(): monster.attack(hero) print(f" {monster.name} hits: {hero.name} has {hero.health}") turn += 1winner = hero.name if hero.health > monster.health else monster.nameprint(f"{winner} wins")1. Ardan hits: Butcher has 116 Butcher hits: Ardan has 1102. Ardan hits: Butcher has 102 Butcher hits: Ardan has 993. Ardan hits: Butcher has 88 Butcher hits: Ardan has 874. Ardan heals: +20 health Butcher hits: Ardan has 945. Ardan hits: Butcher has 74 Butcher hits: Ardan has 806. Ardan hits: Butcher has 60 Butcher hits: Ardan has 657. Ardan heals: +20 health Butcher hits: Ardan has 698. Ardan hits: Butcher has 46 Butcher hits: Ardan has 529. Ardan heals: +20 health Butcher hits: Ardan has 5410. Ardan hits: Butcher has 32 Butcher hits: Ardan has 35Ardan winsconst 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} hits: ${monster.name} has ${monster.health}`); } if (monster.isAlive()) { monster.attack(hero); console.log(` ${monster.name} hits: ${hero.name} has ${hero.health}`); }}const winner = hero.health > monster.health ? hero.name : monster.name;console.log(`${winner} wins`);rl.close();1. Ardan hits: Butcher has 116 Butcher hits: Ardan has 1102. Ardan hits: Butcher has 102 Butcher hits: Ardan has 993. Ardan hits: Butcher has 88 Butcher hits: Ardan has 874. Ardan heals: +20 health Butcher hits: Ardan has 945. Ardan hits: Butcher has 74 Butcher hits: Ardan has 806. Ardan hits: Butcher has 60 Butcher hits: Ardan has 657. Ardan heals: +20 health Butcher hits: Ardan has 698. Ardan hits: Butcher has 46 Butcher hits: Ardan has 529. Ardan heals: +20 health Butcher hits: Ardan has 5410. Ardan hits: Butcher has 32 Butcher hits: Ardan has 35Ardan winsThe input for this example is:
1 1 1 2 1 1 2 1 2 1If the player chooses only attacks, 1 ten times, the paladin falls on turn 9, and the Butcher has 4 health left. With three heals the turns last until the end, and the paladin wins with 35 health against 32.
More precisely, if you are curious
- The choice is read every turn, but if the hero falls, the loop stops and the remaining input lines are not read.
choice.equals("2")compares the text. Everything else, even an empty line, is an attack: the game cannot stop on an unknown choice. You will see how to reject a wrong choice with an exception in the third part.
Try it
Find the bug
The barbarian hits the Skeleton King with power 20, and the king loses 20 instead of 15. Which line in the attack is the bug on?
Pick the line with the bug.
The check finds that the king can defend, but then the damage is subtracted directly, going around defend. Both branches do the same thing. You need defender.defend(power);.
Try it
What is missing?
public void attack(Monster target) {
if (target IDefendable defender) {
defender.defend(power);
} else {
target.takeDamage(power);
}
}instanceof checks the actual object. With a name after the type, defender, the check and the conversion are on one line.Exercise
More battles
Solve these in your editor. Start from the classes in the lesson.
1. A new monster. Andariel, a demon, 100 health and 12 power. Her hit is poisoned: after the normal attack the target takes another 4 damage directly through takeDamage, going around the defense. Run the fight to the death with Grok the barbarian.
1. Grok hits: Andariel has 80 Andariel hits: Grok has 962. Grok hits: Andariel has 60 Andariel hits: Grok has 823. Grok hits: Andariel has 40 Andariel hits: Grok has 684. Grok hits: Andariel has 20 Andariel hits: Grok has 545. Grok hits: Andariel has 0Grok winsGrok takes 10 from the hit (12 minus one fifth) and 4 from the poison: 14 per round.
2. A healing potion. A class Potion with an amount of health. use(Character target) heals the target and returns text. The potion can be drunk once: after that it says "The potion is empty". In the paladin's fight with the Butcher, the hero drinks a potion for 40 instead of hitting when his health is below 30.
1. Ardan hits: Butcher has 116 Butcher hits: Ardan has 1102. Ardan hits: Butcher has 102 Butcher hits: Ardan has 993. Ardan hits: Butcher has 88 Butcher hits: Ardan has 874. Ardan hits: Butcher has 74 Butcher hits: Ardan has 745. Ardan hits: Butcher has 60 Butcher hits: Ardan has 606. Ardan hits: Butcher has 46 Butcher hits: Ardan has 457. Ardan hits: Butcher has 32 Butcher hits: Ardan has 298. Ardan drinks a potion: +40 health Butcher hits: Ardan has 529. Ardan hits: Butcher has 18 Butcher hits: Ardan has 3410. Ardan hits: Butcher has 4 Butcher hits: Ardan has 1511. Ardan hits: Butcher has 0Ardan wins3. A battle report. Instead of a line for every hit, print at the end how many rounds the fight lasted, how much damage the hero dealt, how much he took and how much health he has left. Grok the barbarian against the Skeleton King:
Rounds: 6Damage dealt: 90Damage taken: 65Health left: 45Hint: remember the target's health before the hit and subtract its health after the hit.
More exercises
- Witch against king. Run the fight of Morena the witch with the Skeleton King, with the choices from input. Can she win in 10 turns?
- Party healer. The paladin heals the hero with the least health in an array of three by 10, instead of himself.
- Who hits first? With
new Random(5), decide at the start of each round who hits first.
Test