RPG 2: la batalla
En resumen: Una batalla en un RPG con contratos 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.
El código está en JavaScript, y las clases son de la primera parte: Character, el paladín, el bárbaro y la bruja.
La idea
Los monstruos
Los monstruos tienen su propio padre. 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); }}En Java y C#, Monster es abstracta para que no exista «un monstruo a secas». JavaScript no tiene clases abstractas: new Monster(...) se creará. En el juego nadie lo hace.
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. En Java y C# cada una es una interfaz: un contrato con solo los nombres de los métodos, sin código. JavaScript no tiene interfaces. El contrato es el propio nombre del método: un objeto «puede defenderse» si tiene un método defend. Esto se llama duck typing: «si camina como un pato y hace cuac como un pato, es un pato».
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)- takeDamage: puede recibir golpes. Tanto
CharactercomoMonsterlo tienen. - defend: puede defenderse. La defensa decide cuánto del golpe pasa. Todo héroe lo tiene 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)); }}- heal: puede curar, también a sí mismo. Solo algunos héroes lo tienen. 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
- Los contratos no están escritos en ningún sitio del código, salvo en el comentario. Si te equivocas con el nombre,
defenseen lugar dedefend, nadie te detendrá: la comprobación simplemente dirá que el rey no se defiende.
- 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 tiene heal.
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? typeof target.defend === "function" es true si el objeto tiene un método defend. Si no lo tiene, target.defend es undefined, y typeof da "undefined" para él. Entonces 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.
const party = [new Paladin("Ardan"), new Barbarian("Grok"), new Witch("Morena")];for (const hero of party) { if (typeof hero.heal === "function") { console.log(hero.name); }}El paladín y la bruja tienen un método heal, y el bárbaro no. La comprobación mira el objeto real, no el 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.
const 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`);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:
ask es la ayuda de entrada que ya conoces. Ella y su import están al principio del archivo y no se muestran aquí, y rl.close() está al final.
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 === "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 target.defend(this._power);.
Pruébalo
¿Qué falta?
target.defend?attack(target) {
if ( target.defend === "function") {
target.defend(this._power);
} else {
target.takeDamage(this._power);
}
}typeof da el tipo del valor como texto: "function" para un método, "undefined" para algo que no está.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(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
makeRandom(5), decide al principio de cada ronda quién golpea primero.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Herencia · Polimorfismo · Interfaces sin interface · Comprobaciones de tipo
- Ejercicios sobre el tema: Hagamos un juego