RPG 2: la batalla
En resumen: Una batalla en un RPG con interfaces para «puede recibir golpes», «puede defenderse» y «puede curar»: una pelea a muerte y una pelea de 10 turnos en la que elige el jugador.
Problema
¿Contra quién luchan los héroes?
Los héroes de la primera parte ya tienen vida y habilidades. Ahora necesitan enemigos: los monstruos que surgen de las profundidades de Arcanum. El Chamán Caído es débil, el Rey Esqueleto está cubierto de una armadura de huesos, y el Carnicero se hace más fuerte con cada golpe.
Un monstruo no es un héroe. No tiene habilidad, y además los héroes luchan contra los monstruos. Así que no puede heredar de Character.
Pero el golpe tiene que funcionar en los dos sentidos. Y algunos de los combatientes se defienden: el rey bloquea parte del daño, y el bárbaro también. Si el ataque comprueba quién es quién con un if para cada clase, cada monstruo nuevo necesitará un if nuevo.
La idea
Los monstruos
Los monstruos tienen su propio padre abstracto. Se parece a Character: un nombre, vida, fuerza, una raza. Su ataque golpea al héroe:
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); }}El Chamán Caído es el monstruo más sencillo: solo nombres y números. El Carnicero sobrescribe el ataque: después de cada golpe su fuerza sube 1.
class FallenShaman extends Monster { public FallenShaman() { super("Chamán Caído", 60, 12, "demonio"); }}class FallenShaman : Monster{ public FallenShaman() : base("Chamán Caído", 60, 12, "demonio") { }}class FallenShaman(Monster): def __init__(self): super().__init__("Chamán Caído", 60, 12, "demonio")class FallenShaman extends Monster { constructor() { super("Chamán Caído", 60, 12, "demonio"); }}class Butcher extends Monster { public Butcher() { super("Carnicero", 130, 10, "demonio"); }
@Override public void attack(Character target) { super.attack(target); power += 1; }}class Butcher : Monster{ public Butcher() : base("Carnicero", 130, 10, "demonio") { }
public override void Attack(Character target) { base.Attack(target); Power += 1; }}class Butcher(Monster): def __init__(self): super().__init__("Carnicero", 130, 10, "demonio")
def attack(self, target): super().attack(target) self._power += 1class Butcher extends Monster { constructor() { super("Carnicero", 130, 10, "demonio"); }
attack(target) { super.attack(target); this._power += 1; }}La idea
Tres contratos
Lo que comparten héroes y monstruos no es un padre sino capacidades. Cada una es una interfaz:
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 no tiene interfaces. El contrato es el nombre del método:// puede recibir golpes: takeDamage(amount)// puede defenderse: defend(attackPower)// puede curar: heal(target, amount)- IAttackable: puede recibir golpes. Tanto
CharactercomoMonsterla implementan. - IDefendable: puede defenderse. La defensa decide cuánto del golpe pasa. Todo héroe la implementa a través de
Character, y entre los monstruos solo lo hace el Rey Esqueleto:
class SkeletonKing extends Monster implements IDefendable { public SkeletonKing() { super("Rey Esqueleto", 90, 16, "muerto viviente"); }
@Override public void defend(int attackPower) { takeDamage(Math.max(attackPower - 5, 0)); }}class SkeletonKing : Monster, IDefendable{ public SkeletonKing() : base("Rey Esqueleto", 90, 16, "muerto viviente") { }
public void Defend(int attackPower) { TakeDamage(Math.Max(attackPower - 5, 0)); }}class SkeletonKing(Monster, Defendable): def __init__(self): super().__init__("Rey Esqueleto", 90, 16, "muerto viviente")
def defend(self, attack_power): self.take_damage(max(attack_power - 5, 0))class SkeletonKing extends Monster { constructor() { super("Rey Esqueleto", 90, 16, "muerto viviente"); }
defend(attackPower) { this.takeDamage(Math.max(attackPower - 5, 0)); }}- IHealable: puede curar, también a sí mismo. Solo algunos héroes la implementan. El paladín cura con ella, y el bárbaro no la tiene, pero sobrescribe la defensa: recibe una quinta parte menos de cada golpe.
class Paladin extends Character implements IHealable { public Paladin(String name) { super(name, 120, 14, "humano"); }
@Override public void heal(Character target, int amount) { target.restore(amount); }
@Override public String useAbility() { heal(this, 20); return name + " se cura: +20 de vida"; }}class Paladin : Character, IHealable{ public Paladin(string name) : base(name, 120, 14, "humano") { }
public void Heal(Character target, int amount) { target.Restore(amount); }
public override string UseAbility() { Heal(this, 20); return Name + " se cura: +20 de vida"; }}class Paladin(Character, Healable): def __init__(self, name): super().__init__(name, 120, 14, "humano")
def heal(self, target, amount): target.restore(amount)
def use_ability(self): self.heal(self, 20) return f"{self._name} se cura: +20 de vida"class Paladin extends Character { constructor(name) { super(name, 120, 14, "humano"); }
heal(target, amount) { target.restore(amount); }
useAbility() { this.heal(this, 20); return `${this._name} se cura: +20 de vida`; }}class Barbarian extends Character { public Barbarian(String name) { super(name, 110, 20, "orco"); }
@Override public void defend(int attackPower) { takeDamage(attackPower - attackPower / 5); }
@Override public String useAbility() { power += power / 2; return name + " entra en furia: fuerza " + power; }}class Barbarian : Character{ public Barbarian(string name) : base(name, 110, 20, "orco") { }
public override void Defend(int attackPower) { TakeDamage(attackPower - attackPower / 5); }
public override string UseAbility() { Power += Power / 2; return Name + " entra en furia: fuerza " + Power; }}class Barbarian(Character): def __init__(self, name): super().__init__(name, 110, 20, "orco")
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} entra en furia: fuerza {self._power}"class Barbarian extends Character { constructor(name) { super(name, 110, 20, "orco"); }
defend(attackPower) { this.takeDamage(attackPower - Math.trunc(attackPower / 5)); }
useAbility() { this._power += Math.trunc(this._power / 2); return `${this._name} entra en furia: fuerza ${this._power}`; }}Más precisamente, para curiosos
- En C# los nombres de las interfaces empiezan con
I. En Java el estilo habitual es sin laI:Attackable,Defendable,Healable. Aquí los nombres son iguales en los dos lenguajes, para que leas el mismo código.
- En la primera parte, el bárbaro sobrescribía
takeDamage. Ahora la defensa tiene su propio nombre.takeDamagesolo resta el daño, ydefenddecide cuánto daño pasa. Así también puede haber daño que esquiva la defensa: veneno, una trampa. restoreestá enCharacter, para que todo héroe pueda ser curado. Solo puede curar quien implementa IHealable.
Míralo
Un golpe, dos objetivos
Este es el Character nuevo. Además de los contratos, tiene un ataque:
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); } }}El ataque le pregunta al objetivo: ¿puedes defenderte? target instanceof IDefendable defender es true si el objeto implementa la interfaz. Entonces defender es el mismo objeto, pero como IDefendable, y puedes llamar a defend sobre él. Si no, el daño se resta directamente.
Grok el bárbaro golpea al chamán y al rey. Después el chamán lo golpea a él:
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());Chamán Caído: 40Rey Esqueleto: 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);Chamán Caído: 40Rey Esqueleto: 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}")Chamán Caído: 40Rey Esqueleto: 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}`);Chamán Caído: 40Rey Esqueleto: 75Grok: 100El chamán pierde toda la fuerza de Grok, 20. El rey bloquea 5 y pierde 15. El chamán golpea con 12, y Grok recibe 12 − 2 = 10. El ataque no sabe nada del rey ni del bárbaro: solo conoce el contrato.
Pruébalo
¿Quién puede curar?
¿Qué imprimirá el programa? Las clases son las de las tarjetas de arriba.
Character[] party = { new Paladin("Ardan"), new Barbarian("Grok"), new Witch("Morena") };for (Character hero : party) { if (hero instanceof IHealable) { System.out.println(hero.getName()); }}El paladín y la bruja implementan IHealable, y el bárbaro no. La comprobación mira el objeto real, no el tipo del array.
Míralo
Una pelea a muerte
Ardan el paladín contra el Chamán Caído. Cada ronda es un golpe del héroe y, si el monstruo sigue vivo, un golpe del monstruo. La pelea termina cuando uno de los dos cae:
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() + " golpea: " + monster.getName() + " tiene " + monster.getHealth()); if (monster.isAlive()) { monster.attack(hero); System.out.println(" " + monster.getName() + " golpea: " + hero.getName() + " tiene " + hero.getHealth()); } round++;}System.out.println(hero.isAlive() ? hero.getName() + " gana" : monster.getName() + " gana");1. Ardan golpea: Chamán Caído tiene 46 Chamán Caído golpea: Ardan tiene 1082. Ardan golpea: Chamán Caído tiene 32 Chamán Caído golpea: Ardan tiene 963. Ardan golpea: Chamán Caído tiene 18 Chamán Caído golpea: Ardan tiene 844. Ardan golpea: Chamán Caído tiene 4 Chamán Caído golpea: Ardan tiene 725. Ardan golpea: Chamán Caído tiene 0Ardan ganaCharacter hero = new Paladin("Ardan");Monster monster = new FallenShaman();int round = 1;while (hero.IsAlive() && monster.IsAlive()){ hero.Attack(monster); Console.WriteLine(round + ". " + hero.Name + " golpea: " + monster.Name + " tiene " + monster.Health); if (monster.IsAlive()) { monster.Attack(hero); Console.WriteLine(" " + monster.Name + " golpea: " + hero.Name + " tiene " + hero.Health); } round++;}Console.WriteLine(hero.IsAlive() ? hero.Name + " gana" : monster.Name + " gana");1. Ardan golpea: Chamán Caído tiene 46 Chamán Caído golpea: Ardan tiene 1082. Ardan golpea: Chamán Caído tiene 32 Chamán Caído golpea: Ardan tiene 963. Ardan golpea: Chamán Caído tiene 18 Chamán Caído golpea: Ardan tiene 844. Ardan golpea: Chamán Caído tiene 4 Chamán Caído golpea: Ardan tiene 725. Ardan golpea: Chamán Caído tiene 0Ardan ganahero = Paladin("Ardan")monster = FallenShaman()round_number = 1while hero.is_alive() and monster.is_alive(): hero.attack(monster) print(f"{round_number}. {hero.name} golpea: {monster.name} tiene {monster.health}") if monster.is_alive(): monster.attack(hero) print(f" {monster.name} golpea: {hero.name} tiene {hero.health}") round_number += 1print(f"{hero.name} gana" if hero.is_alive() else f"{monster.name} gana")1. Ardan golpea: Chamán Caído tiene 46 Chamán Caído golpea: Ardan tiene 1082. Ardan golpea: Chamán Caído tiene 32 Chamán Caído golpea: Ardan tiene 963. Ardan golpea: Chamán Caído tiene 18 Chamán Caído golpea: Ardan tiene 844. Ardan golpea: Chamán Caído tiene 4 Chamán Caído golpea: Ardan tiene 725. Ardan golpea: Chamán Caído tiene 0Ardan ganaconst hero = new Paladin("Ardan");const monster = new FallenShaman();let round = 1;while (hero.isAlive() && monster.isAlive()) { hero.attack(monster); console.log(`${round}. ${hero.name} golpea: ${monster.name} tiene ${monster.health}`); if (monster.isAlive()) { monster.attack(hero); console.log(` ${monster.name} golpea: ${hero.name} tiene ${hero.health}`); } round++;}console.log(hero.isAlive() ? `${hero.name} gana` : `${monster.name} gana`);1. Ardan golpea: Chamán Caído tiene 46 Chamán Caído golpea: Ardan tiene 1082. Ardan golpea: Chamán Caído tiene 32 Chamán Caído golpea: Ardan tiene 963. Ardan golpea: Chamán Caído tiene 18 Chamán Caído golpea: Ardan tiene 844. Ardan golpea: Chamán Caído tiene 4 Chamán Caído golpea: Ardan tiene 725. Ardan golpea: Chamán Caído tiene 0Ardan ganaEl bucle es el bucle de juego de la lección «Cómo pensar un juego»: el estado es la vida de los dos combatientes, las reglas son los ataques, y el final es la condición del 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() + " golpea: " + monster.getName() + " tiene " + monster.getHealth()); if (monster.isAlive()) { monster.attack(hero); System.out.println(" " + monster.getName() + " golpea: " + hero.getName() + " tiene " + hero.getHealth()); } round++;}System.out.println(hero.isAlive() ? hero.getName() + " gana" : monster.getName() + " gana");Paso a paso, en palabras
- Ardan empieza con 120 de vida, Chamán Caído con 60. Las rondas empiezan en 1.
- Ronda 1: Ardan golpea y quita 14. Chamán Caído baja de 60 a 46.
- Chamán Caído está vivo y contraataca. Quita 12: Ardan baja de 120 a 108.
- Ronda 2: Ardan golpea y quita 14. Chamán Caído baja de 46 a 32.
- Chamán Caído está vivo y contraataca. Quita 12: Ardan baja de 108 a 96.
- Ronda 3: Ardan golpea y quita 14. Chamán Caído baja de 32 a 18.
- Chamán Caído está vivo y contraataca. Quita 12: Ardan baja de 96 a 84.
- Ronda 4: Ardan golpea y quita 14. Chamán Caído baja de 18 a 4.
- Chamán Caído está vivo y contraataca. Quita 12: Ardan baja de 84 a 72.
- Ronda 5: Ardan golpea y Chamán Caído baja de 4 a 0. monster.isAlive() es false, así que no contraataca.
- La condición del while ahora es false: uno de los dos ya no está vivo. El bucle se detiene, y Ardan gana.
Míralo
10 turnos y la elección del jugador
Contra el Carnicero los ataques solos no bastan. Por eso el jugador elige en cada turno: 1 es un ataque, 2 es la habilidad del héroe. La pelea dura como máximo 10 turnos, y si al final los dos siguen vivos, gana el que tenga más vida:
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() + " golpea: " + monster.getName() + " tiene " + monster.getHealth()); } if (monster.isAlive()) { monster.attack(hero); System.out.println(" " + monster.getName() + " golpea: " + hero.getName() + " tiene " + hero.getHealth()); }}String winner = hero.getHealth() > monster.getHealth() ? hero.getName() : monster.getName();System.out.println(winner + " gana");1. Ardan golpea: Carnicero tiene 116 Carnicero golpea: Ardan tiene 1102. Ardan golpea: Carnicero tiene 102 Carnicero golpea: Ardan tiene 993. Ardan golpea: Carnicero tiene 88 Carnicero golpea: Ardan tiene 874. Ardan se cura: +20 de vida Carnicero golpea: Ardan tiene 945. Ardan golpea: Carnicero tiene 74 Carnicero golpea: Ardan tiene 806. Ardan golpea: Carnicero tiene 60 Carnicero golpea: Ardan tiene 657. Ardan se cura: +20 de vida Carnicero golpea: Ardan tiene 698. Ardan golpea: Carnicero tiene 46 Carnicero golpea: Ardan tiene 529. Ardan se cura: +20 de vida Carnicero golpea: Ardan tiene 5410. Ardan golpea: Carnicero tiene 32 Carnicero golpea: Ardan tiene 35Ardan ganaCharacter 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 + " golpea: " + monster.Name + " tiene " + monster.Health); } if (monster.IsAlive()) { monster.Attack(hero); Console.WriteLine(" " + monster.Name + " golpea: " + hero.Name + " tiene " + hero.Health); }}string winner = hero.Health > monster.Health ? hero.Name : monster.Name;Console.WriteLine(winner + " gana");1. Ardan golpea: Carnicero tiene 116 Carnicero golpea: Ardan tiene 1102. Ardan golpea: Carnicero tiene 102 Carnicero golpea: Ardan tiene 993. Ardan golpea: Carnicero tiene 88 Carnicero golpea: Ardan tiene 874. Ardan se cura: +20 de vida Carnicero golpea: Ardan tiene 945. Ardan golpea: Carnicero tiene 74 Carnicero golpea: Ardan tiene 806. Ardan golpea: Carnicero tiene 60 Carnicero golpea: Ardan tiene 657. Ardan se cura: +20 de vida Carnicero golpea: Ardan tiene 698. Ardan golpea: Carnicero tiene 46 Carnicero golpea: Ardan tiene 529. Ardan se cura: +20 de vida Carnicero golpea: Ardan tiene 5410. Ardan golpea: Carnicero tiene 32 Carnicero golpea: Ardan tiene 35Ardan ganahero = 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} golpea: {monster.name} tiene {monster.health}") if monster.is_alive(): monster.attack(hero) print(f" {monster.name} golpea: {hero.name} tiene {hero.health}") turn += 1winner = hero.name if hero.health > monster.health else monster.nameprint(f"{winner} gana")1. Ardan golpea: Carnicero tiene 116 Carnicero golpea: Ardan tiene 1102. Ardan golpea: Carnicero tiene 102 Carnicero golpea: Ardan tiene 993. Ardan golpea: Carnicero tiene 88 Carnicero golpea: Ardan tiene 874. Ardan se cura: +20 de vida Carnicero golpea: Ardan tiene 945. Ardan golpea: Carnicero tiene 74 Carnicero golpea: Ardan tiene 806. Ardan golpea: Carnicero tiene 60 Carnicero golpea: Ardan tiene 657. Ardan se cura: +20 de vida Carnicero golpea: Ardan tiene 698. Ardan golpea: Carnicero tiene 46 Carnicero golpea: Ardan tiene 529. Ardan se cura: +20 de vida Carnicero golpea: Ardan tiene 5410. Ardan golpea: Carnicero tiene 32 Carnicero golpea: Ardan tiene 35Ardan ganaconst 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} golpea: ${monster.name} tiene ${monster.health}`); } if (monster.isAlive()) { monster.attack(hero); console.log(` ${monster.name} golpea: ${hero.name} tiene ${hero.health}`); }}const winner = hero.health > monster.health ? hero.name : monster.name;console.log(`${winner} gana`);rl.close();1. Ardan golpea: Carnicero tiene 116 Carnicero golpea: Ardan tiene 1102. Ardan golpea: Carnicero tiene 102 Carnicero golpea: Ardan tiene 993. Ardan golpea: Carnicero tiene 88 Carnicero golpea: Ardan tiene 874. Ardan se cura: +20 de vida Carnicero golpea: Ardan tiene 945. Ardan golpea: Carnicero tiene 74 Carnicero golpea: Ardan tiene 806. Ardan golpea: Carnicero tiene 60 Carnicero golpea: Ardan tiene 657. Ardan se cura: +20 de vida Carnicero golpea: Ardan tiene 698. Ardan golpea: Carnicero tiene 46 Carnicero golpea: Ardan tiene 529. Ardan se cura: +20 de vida Carnicero golpea: Ardan tiene 5410. Ardan golpea: Carnicero tiene 32 Carnicero golpea: Ardan tiene 35Ardan ganaLa entrada de este ejemplo es:
1 1 1 2 1 1 2 1 2 1Si el jugador elige solo ataques, diez veces 1, el paladín cae en el turno 9, y al Carnicero le quedan 4 de vida. Con tres curaciones los turnos llegan hasta el final, y el paladín gana con 35 de vida contra 32.
Más precisamente, para curiosos
- La elección se lee en cada turno, pero si el héroe cae, el bucle se detiene y las líneas de entrada que quedan no se leen.
choice.equals("2")compara el texto. Todo lo demás, incluso una línea vacía, es un ataque: el juego no puede detenerse ante una elección desconocida. En la tercera parte verás cómo rechazar una elección incorrecta con una excepción.
Pruébalo
Encuentra el error
El bárbaro golpea al Rey Esqueleto con fuerza 20, y el rey pierde 20 en lugar de 15. ¿En qué línea del ataque está el error?
Elige la línea con el error.
La comprobación descubre que el rey puede defenderse, pero después el daño se resta directamente, esquivando defend. Las dos ramas hacen lo mismo. Necesitas defender.defend(power);.
Pruébalo
¿Qué falta?
public void attack(Monster target) {
if (target IDefendable defender) {
defender.defend(power);
} else {
target.takeDamage(power);
}
}instanceof comprueba el objeto real. Con un nombre detrás del tipo, defender, la comprobación y la conversión están en una sola línea.Ejercicio
Más batallas
Resuelve estos ejercicios en tu editor. Empieza con las clases de la lección.
1. Un monstruo nuevo. Andariel, una demonia, 100 de vida y 12 de fuerza. Su golpe está envenenado: después del ataque normal, el objetivo recibe otros 4 de daño directamente con takeDamage, esquivando la defensa. Ejecuta la pelea a muerte con Grok el bárbaro.
1. Grok golpea: Andariel tiene 80 Andariel golpea: Grok tiene 962. Grok golpea: Andariel tiene 60 Andariel golpea: Grok tiene 823. Grok golpea: Andariel tiene 40 Andariel golpea: Grok tiene 684. Grok golpea: Andariel tiene 20 Andariel golpea: Grok tiene 545. Grok golpea: Andariel tiene 0Grok ganaGrok recibe 10 del golpe (12 menos una quinta parte) y 4 del veneno: 14 por ronda.
2. Una poción curativa. Una clase Potion con una cantidad de vida. use(Character target) cura al objetivo y devuelve un texto. La poción se bebe una vez: después dice «La poción está vacía». En la pelea del paladín con el Carnicero, el héroe bebe una poción de 40 en lugar de golpear cuando su vida está por debajo de 30.
1. Ardan golpea: Carnicero tiene 116 Carnicero golpea: Ardan tiene 1102. Ardan golpea: Carnicero tiene 102 Carnicero golpea: Ardan tiene 993. Ardan golpea: Carnicero tiene 88 Carnicero golpea: Ardan tiene 874. Ardan golpea: Carnicero tiene 74 Carnicero golpea: Ardan tiene 745. Ardan golpea: Carnicero tiene 60 Carnicero golpea: Ardan tiene 606. Ardan golpea: Carnicero tiene 46 Carnicero golpea: Ardan tiene 457. Ardan golpea: Carnicero tiene 32 Carnicero golpea: Ardan tiene 298. Ardan bebe una poción: +40 de vida Carnicero golpea: Ardan tiene 529. Ardan golpea: Carnicero tiene 18 Carnicero golpea: Ardan tiene 3410. Ardan golpea: Carnicero tiene 4 Carnicero golpea: Ardan tiene 1511. Ardan golpea: Carnicero tiene 0Ardan gana3. Un informe de batalla. En lugar de una línea por cada golpe, imprime al final cuántas rondas duró la pelea, cuánto daño causó el héroe, cuánto recibió y cuánta vida le queda. Grok el bárbaro contra el Rey Esqueleto:
Rondas: 6Daño causado: 90Daño recibido: 65Vida restante: 45Pista: recuerda la vida del objetivo antes del golpe y réstale su vida después del golpe.
Más ejercicios
- Bruja contra rey. Ejecuta la pelea de Morena la bruja con el Rey Esqueleto, con las elecciones desde la entrada. ¿Puede ganar en 10 turnos?
- Sanador del grupo. El paladín cura 10 al héroe con menos vida en un array de tres, en lugar de curarse a sí mismo.
- ¿Quién golpea primero? Con
new Random(5), decide al principio de cada ronda quién golpea primero.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Polimorfismo · Clases abstractas · Interfaces · Comprobaciones de tipo
- Ejercicios sobre el tema: Hagamos un juego