RPG 3: el juego completo
En resumen: El RPG completo: una clase Game lleva al héroe por batallas, descanso, niveles y experiencia, y un movimiento incorrecto lanza GameException, que el juego captura para seguir adelante.
Problema
De una pelea a un juego
Los héroes y los monstruos ya luchan. Pero un juego es más que una pelea: creas un héroe, alineas los monstruos contra los que va a pelear, y los va superando uno por uno. Entre las peleas descansa o se cura con magia.
Si todo esto está en main, ahí estarán el héroe, un array de monstruos, el número del siguiente, contadores y comprobaciones. ¿Y quién va a impedir que el jugador empiece una pelea sin héroe, o que le pida magia a un bárbaro?
El estado del juego y sus reglas tienen que estar en un solo lugar: en una clase Game.
La idea
La clase Game
El héroe y los monstruos son el estado del juego. Los métodos de Game son las reglas: qué puede hacer el jugador y qué pasa después. La primera es crear un héroe:
private Character hero;private Monster[] monstruos = new Monster[5];private int count;private int next;private Random random;
public Game(Random random) { this.random = random;}
public void createHero(String name, String race) { if (race.equals("paladín")) { hero = new Paladin(name); } else if (race.equals("bárbaro")) { hero = new Barbarian(name); } else if (race.equals("bruja")) { hero = new Witch(name); } else { throw new GameException("No existe esa raza: " + race); }}private Character hero;private Monster[] monstruos = new Monster[5];private int count;private int next;private Random random;
public Game(Random random){ this.random = random;}
public void CreateHero(string name, string race){ if (race == "paladín") { hero = new Paladin(name); } else if (race == "bárbaro") { hero = new Barbarian(name); } else if (race == "bruja") { hero = new Witch(name); } else { throw new GameException("No existe esa raza: " + race); }}def __init__(self, random): self._hero = None self._monsters = [None] * 5 self._count = 0 self._next = 0 self._random = random
def create_hero(self, name, race): if race == "paladín": self._hero = Paladin(name) elif race == "bárbaro": self._hero = Barbarian(name) elif race == "bruja": self._hero = Witch(name) else: raise GameError(f"No existe esa raza: {race}")#hero = null;#monsters = new Array(5);#count = 0;#next = 0;#random;
constructor(random) { this.#random = random;}
createHero(name, race) { if (race === "paladín") { this.#hero = new Paladin(name); } else if (race === "bárbaro") { this.#hero = new Barbarian(name); } else if (race === "bruja") { this.#hero = new Witch(name); } else { throw new GameError(`No existe esa raza: ${race}`); }}new Random(), y en los ejemplos tiene una semilla, para que la salida sea siempre la misma.Una raza desconocida no es motivo para crear un héroe cualquiera. El método lanza GameException: nuestra propia excepción para cada movimiento que las reglas no permiten. Tiene su propia tarjeta más adelante.
La idea
Una fila de monstruos
Los monstruos esperan en una cola: el que se añadió primero es la primera pelea. Aquí la cola es un array y dos índices:
public void addMonster(Monster monster) { if (count == monstruos.length) { throw new GameException("La fila está llena: como máximo " + monstruos.length + " monstruos"); } monstruos[count] = monster; count++;}
public Monster createRandomMonster() { int kind = random.nextInt(3); if (kind == 0) { return new FallenShaman(); } if (kind == 1) { return new SkeletonKing(); } return new Butcher();}
public boolean hasMonsters() { return next < count;}public void AddMonster(Monster monster){ if (count == monstruos.Length) { throw new GameException("La fila está llena: como máximo " + monstruos.Length + " monstruos"); } monstruos[count] = monster; count++;}
public Monster CreateRandomMonster(){ int kind = random.Next(3); if (kind == 0) { return new FallenShaman(); } if (kind == 1) { return new SkeletonKing(); } return new Butcher();}
public bool HasMonsters(){ return next < count;}def add_monster(self, monster): if self._count == len(self._monsters): raise GameError(f"La fila está llena: como máximo {len(self._monsters)} monstruos") self._monsters[self._count] = monster self._count += 1
def create_random_monster(self): kind = self._random.randrange(3) if kind == 0: return FallenShaman() if kind == 1: return SkeletonKing() return Butcher()
def has_monsters(self): return self._next < self._countaddMonster(monster) { if (this.#count === this.#monsters.length) { throw new GameError(`La fila está llena: como máximo ${this.#monsters.length} monstruos`); } this.#monsters[this.#count] = monster; this.#count++;}
createRandomMonster() { const kind = Math.floor(this.#random() * 3); if (kind === 0) { return new FallenShaman(); } if (kind === 1) { return new SkeletonKing(); } return new Butcher();}
hasMonsters() { return this.#next < this.#count;}countes cuántos monstruos se han añadido. El nuevo va amonsters[count].nextes cuál pelea a continuación. Después de cada pelea sube 1, y cuando llega acount, la fila se acaba.
addMonster lanza GameException antes de escribir fuera del array.createRandomMonster es el extra del ejercicio antiguo: un monstruo elegido al azar.
Más precisamente, para curiosos
-
El ejercicio antiguo guardaba los monstruos en una cola de verdad,
Queue<Monster>. Una cola recuerda por sí misma cuál es el primero, y nunca se llena. Llega en el módulo Colecciones (ArrayDeque): ahí desaparecencountynext, y la fila crece lo que haga falta. - Los monstruos anteriores a
nextse quedan en el array. No hacen daño: nadie los lee ya.
La idea
Niveles y experiencia
Después de cada victoria el héroe recibe experiencia igual a la vida del monstruo. En el nivel 1 necesita 100 de experiencia para llegar al nivel 2, en el nivel 2 necesita 200 para llegar al 3, y así sucesivamente. Un nivel nuevo da 10 de vida más, 3 de fuerza más y la vida completa:
public boolean gainExperience(int exp) { experience += exp; boolean up = false; while (experience >= level * 100) { experience -= level * 100; levelUp(); up = true; } return up;}
public void levelUp() { level++; maxHealth += 10; power += 3; health = maxHealth;}public bool GainExperience(int exp){ Experience += exp; bool up = false; while (Experience >= Level * 100) { Experience -= Level * 100; LevelUp(); up = true; } return up;}
public void LevelUp(){ Level++; MaxHealth += 10; Power += 3; Health = MaxHealth;}def gain_experience(self, exp): self._experience += exp up = False while self._experience >= self._level * 100: self._experience -= self._level * 100 self.level_up() up = True return up
def level_up(self): self._level += 1 self._max_health += 10 self._power += 3 self._health = self._max_healthgainExperience(exp) { this._experience += exp; let up = false; while (this._experience >= this._level * 100) { this._experience -= this._level * 100; this.levelUp(); up = true; } return up;}
levelUp() { this._level++; this._maxHealth += 10; this._power += 3; this._health = this._maxHealth;}Los métodos están en Character. La experiencia que pasa del umbral se guarda para el nivel siguiente. El bucle while es para el caso en que una victoria alcanza para dos niveles.
Los campos maxHealth, level y experience son nuevos en Character. Con maxHealth, curar ya no sube la vida por encima del máximo.
Pruébalo
¿En qué nivel está el bárbaro?
Grok empieza en el nivel 1 con fuerza 20. Gana contra el chamán (60 de experiencia) y contra el rey (90 de experiencia).
¿Qué imprimirá el programa? Nivel, experiencia, fuerza.
public boolean gainExperience(int exp) { experience += exp; boolean up = false; while (experience >= level * 100) { experience -= level * 100; levelUp(); up = true; } return up;}
public void levelUp() { level++; maxHealth += 10; power += 3; health = maxHealth;}
Character grok = new Barbarian("Grok");grok.gainExperience(60);grok.gainExperience(90);System.out.println(grok.getLevel() + " " + grok.getExperience() + " " + grok.getPower());60 + 90 = 150 de experiencia. En el nivel 1 el umbral es 100: Grok sube al nivel 2, y sobran 50. El nivel 3 cuesta 200. La fuerza es 20 + 3 = 23.
Míralo
La batalla y el descanso
startBattle primero comprueba si la pelea es posible. Después toma el siguiente monstruo y ejecuta la pelea a muerte de la segunda parte. Devuelve si el héroe sobrevivió:
public boolean startBattle() { if (hero == null) { throw new GameException("Crea primero un héroe"); } if (!hasMonsters()) { throw new GameException("No hay monstruos en la fila"); } Monster monster = monstruos[next]; next++; int rounds = 0; while (hero.isAlive() && monster.isAlive()) { rounds++; hero.attack(monster); if (monster.isAlive()) { monster.attack(hero); } } if (!hero.isAlive()) { System.out.println(hero.getName() + " cae en la ronda " + rounds + ". Ganador: " + monster.getName()); return false; } System.out.println(monster.getName() + " es derrotado en " + rounds + " rondas. " + hero.getName() + ": vida " + hero.getHealth() + "/" + hero.getMaxHealth()); if (hero.gainExperience(monster.getReward())) { System.out.println(hero.getName() + " llega al nivel " + hero.getLevel() + ": vida " + hero.getMaxHealth() + ", fuerza " + hero.getPower()); } return true;}public bool StartBattle(){ if (hero == null) { throw new GameException("Crea primero un héroe"); } if (!HasMonsters()) { throw new GameException("No hay monstruos en la fila"); } Monster monster = monstruos[next]; next++; int rounds = 0; while (hero.IsAlive() && monster.IsAlive()) { rounds++; hero.Attack(monster); if (monster.IsAlive()) { monster.Attack(hero); } } if (!hero.IsAlive()) { Console.WriteLine(hero.Name + " cae en la ronda " + rounds + ". Ganador: " + monster.Name); return false; } Console.WriteLine(monster.Name + " es derrotado en " + rounds + " rondas. " + hero.Name + ": vida " + hero.Health + "/" + hero.MaxHealth); if (hero.GainExperience(monster.Reward)) { Console.WriteLine(hero.Name + " llega al nivel " + hero.Level + ": vida " + hero.MaxHealth + ", fuerza " + hero.Power); } return true;}def start_battle(self): if self._hero is None: raise GameError("Crea primero un héroe") if not self.has_monsters(): raise GameError("No hay monstruos en la fila") hero = self._hero monster = self._monsters[self._next] self._next += 1 rounds = 0 while hero.is_alive() and monster.is_alive(): rounds += 1 hero.attack(monster) if monster.is_alive(): monster.attack(hero) if not hero.is_alive(): print(f"{hero.name} cae en la ronda {rounds}. Ganador: {monster.name}") return False print(f"{monster.name} es derrotado en {rounds} rondas. {hero.name}: vida {hero.health}/{hero.max_health}") if hero.gain_experience(monster.reward): print(f"{hero.name} llega al nivel {hero.level}: vida {hero.max_health}, fuerza {hero.power}") return TruestartBattle() { if (this.#hero === null) { throw new GameError("Crea primero un héroe"); } if (!this.hasMonsters()) { throw new GameError("No hay monstruos en la fila"); } const hero = this.#hero; const monster = this.#monsters[this.#next]; this.#next++; let rounds = 0; while (hero.isAlive() && monster.isAlive()) { rounds++; hero.attack(monster); if (monster.isAlive()) { monster.attack(hero); } } if (!hero.isAlive()) { console.log(`${hero.name} cae en la ronda ${rounds}. Ganador: ${monster.name}`); return false; } console.log(`${monster.name} es derrotado en ${rounds} rondas. ${hero.name}: vida ${hero.health}/${hero.maxHealth}`); if (hero.gainExperience(monster.reward)) { console.log(`${hero.name} llega al nivel ${hero.level}: vida ${hero.maxHealth}, fuerza ${hero.power}`); } return true;}recover es la elección después de la pelea. Solo un héroe que implementa IHealable puede usar magia. El descanso son 5 turnos de 10 de vida:
public void recover(String way) { if (way.equals("descanso")) { for (int turn = 0; turn < 5; turn++) { hero.restore(10); } System.out.println(hero.getName() + " descansa 5 turnos: vida " + hero.getHealth() + "/" + hero.getMaxHealth()); } else if (way.equals("magia")) { if (!(hero instanceof IHealable)) { throw new GameException(hero.getName() + " no domina la magia curativa"); } System.out.println(hero.useAbility()); } else { throw new GameException("Elección desconocida «" + way + "»: ¿descanso o magia?"); }}public void Recover(string way){ if (way == "descanso") { for (int turn = 0; turn < 5; turn++) { hero.Restore(10); } Console.WriteLine(hero.Name + " descansa 5 turnos: vida " + hero.Health + "/" + hero.MaxHealth); } else if (way == "magia") { if (!(hero is IHealable)) { throw new GameException(hero.Name + " no domina la magia curativa"); } Console.WriteLine(hero.UseAbility()); } else { throw new GameException("Elección desconocida «" + way + "»: ¿descanso o magia?"); }}def recover(self, way): hero = self._hero if way == "descanso": for _ in range(5): hero.restore(10) print(f"{hero.name} descansa 5 turnos: vida {hero.health}/{hero.max_health}") elif way == "magia": if not isinstance(hero, Healable): raise GameError(f"{hero.name} no domina la magia curativa") print(hero.use_ability()) else: raise GameError(f"Elección desconocida «{way}»: ¿descanso o magia?")recover(way) { const hero = this.#hero; if (way === "descanso") { for (let turn = 0; turn < 5; turn++) { hero.restore(10); } console.log(`${hero.name} descansa 5 turnos: vida ${hero.health}/${hero.maxHealth}`); } else if (way === "magia") { if (typeof hero.heal !== "function") { throw new GameError(`${hero.name} no domina la magia curativa`); } console.log(hero.useAbility()); } else { throw new GameError(`Elección desconocida «${way}»: ¿descanso o magia?`); }}En el ejercicio antiguo el héroe esperaba 5 segundos. Aquí el descanso se cuenta en turnos, porque en un juego por turnos el tiempo se mide en turnos, y la comprobación del juego no debería tener que esperar.
Míralo
El juego completo
Grok el bárbaro contra el chamán y dos monstruos aleatorios. Después de cada pelea descansa. Si cae, el juego termina:
Game game = new Game(new Random(12));game.createHero("Grok", "bárbaro");game.addMonster(new FallenShaman());game.addMonster(game.createRandomMonster());game.addMonster(game.createRandomMonster());while (game.hasMonsters()) { if (!game.startBattle()) { System.out.println("Fin del juego"); break; } game.recover("descanso");}Chamán Caído es derrotado en 3 rondas. Grok: vida 90/110Grok descansa 5 turnos: vida 110/110Chamán Caído es derrotado en 3 rondas. Grok: vida 90/110Grok llega al nivel 2: vida 120, fuerza 23Grok descansa 5 turnos: vida 120/120Carnicero es derrotado en 6 rondas. Grok: vida 70/120Grok descansa 5 turnos: vida 120/120Game game = new Game(new Random(12));game.CreateHero("Grok", "bárbaro");game.AddMonster(new FallenShaman());game.AddMonster(game.CreateRandomMonster());game.AddMonster(game.CreateRandomMonster());while (game.HasMonsters()){ if (!game.StartBattle()) { Console.WriteLine("Fin del juego"); break; } game.Recover("descanso");}Chamán Caído es derrotado en 3 rondas. Grok: vida 90/110Grok descansa 5 turnos: vida 110/110Carnicero es derrotado en 7 rondas. Grok: vida 48/110Grok llega al nivel 2: vida 120, fuerza 23Grok descansa 5 turnos: vida 120/120Rey Esqueleto es derrotado en 5 rondas. Grok: vida 68/120Grok descansa 5 turnos: vida 118/120game = Game(Random(12))game.create_hero("Grok", "bárbaro")game.add_monster(FallenShaman())game.add_monster(game.create_random_monster())game.add_monster(game.create_random_monster())while game.has_monsters(): if not game.start_battle(): print("Fin del juego") break game.recover("descanso")Chamán Caído es derrotado en 3 rondas. Grok: vida 90/110Grok descansa 5 turnos: vida 110/110Rey Esqueleto es derrotado en 6 rondas. Grok: vida 45/110Grok llega al nivel 2: vida 120, fuerza 23Grok descansa 5 turnos: vida 120/120Rey Esqueleto es derrotado en 5 rondas. Grok: vida 68/120Grok descansa 5 turnos: vida 118/120const game = new Game(makeRandom(12));game.createHero("Grok", "bárbaro");game.addMonster(new FallenShaman());game.addMonster(game.createRandomMonster());game.addMonster(game.createRandomMonster());while (game.hasMonsters()) { if (!game.startBattle()) { console.log("Fin del juego"); break; } game.recover("descanso");}Chamán Caído es derrotado en 3 rondas. Grok: vida 90/110Grok descansa 5 turnos: vida 110/110Rey Esqueleto es derrotado en 6 rondas. Grok: vida 45/110Grok llega al nivel 2: vida 120, fuerza 23Grok descansa 5 turnos: vida 120/120Rey Esqueleto es derrotado en 5 rondas. Grok: vida 68/120Grok descansa 5 turnos: vida 118/120Game game = new Game(new Random(12));game.createHero("Grok", "bárbaro");game.addMonster(new FallenShaman());game.addMonster(game.createRandomMonster());game.addMonster(game.createRandomMonster());while (game.hasMonsters()) { if (!game.startBattle()) { System.out.println("Fin del juego"); break; } game.recover("descanso");}Paso a paso, en palabras
- El juego recibe un generador con semilla 12. El héroe es Grok, y el array tiene 5 lugares, todavía vacíos.
- Cada addMonster() escribe en monsters[count] y sube count. El segundo y el tercero son aleatorios: Chamán Caído y Carnicero.
- next vale 0: sale Chamán Caído. Es derrotado en 3 rondas, y a Grok le quedan 90/110.
- next pasa a 1. El descanso son 5 turnos de 10 de vida: 110/110.
- next vale 1: sale Chamán Caído. Es derrotado en 3 rondas, y a Grok le quedan 90/110. La experiencia alcanza para el nivel 2: vida 120, fuerza 23.
- next pasa a 2. El descanso son 5 turnos de 10 de vida: 120/120.
- next vale 2: sale Carnicero. Es derrotado en 6 rondas, y a Grok le quedan 70/120.
- next pasa a 3. El descanso son 5 turnos de 10 de vida: 120/120.
- next vale 3, igual que count: hasMonsters() devuelve false, y el bucle termina.
La idea
Un movimiento incorrecto: GameException
El jugador puede pedir una pelea sin héroe, una raza que no existe, o magia a un bárbaro. Eso no es un error del programa. Es un movimiento que las reglas no permiten. Los métodos de Game lanzan para eso su propia excepción:
class GameException extends RuntimeException { public GameException(String message) { super(message); }}class GameException : Exception{ public GameException(string message) : base(message) { }}class GameError(Exception): passclass GameError extends Error { constructor(message) { super(message); this.name = "GameError"; }}El juego captura solo esa, le dice al jugador por qué el movimiento no está permitido y sigue con el movimiento siguiente:
Game game = new Game(new Random(12));String[] commands = { "pelea", "héroe Grok dragón", "héroe Grok bárbaro", "magia", "dormir" };for (String command : commands) { String[] words = command.split(" "); try { if (words[0].equals("pelea")) { game.startBattle(); } else if (words[0].equals("héroe")) { game.createHero(words[1], words[2]); System.out.println("El héroe es " + words[1]); } else { game.recover(words[0]); } } catch (GameException e) { System.out.println("No se puede: " + e.getMessage()); }}No se puede: Crea primero un héroeNo se puede: No existe esa raza: dragónEl héroe es GrokNo se puede: Grok no domina la magia curativaNo se puede: Elección desconocida «dormir»: ¿descanso o magia?Game game = new Game(new Random(12));string[] commands = { "pelea", "héroe Grok dragón", "héroe Grok bárbaro", "magia", "dormir" };foreach (string command in commands){ string[] words = command.Split(' '); try { if (words[0] == "pelea") { game.StartBattle(); } else if (words[0] == "héroe") { game.CreateHero(words[1], words[2]); Console.WriteLine("El héroe es " + words[1]); } else { game.Recover(words[0]); } } catch (GameException e) { Console.WriteLine("No se puede: " + e.Message); }}No se puede: Crea primero un héroeNo se puede: No existe esa raza: dragónEl héroe es GrokNo se puede: Grok no domina la magia curativaNo se puede: Elección desconocida «dormir»: ¿descanso o magia?game = Game(Random(12))commands = ["pelea", "héroe Grok dragón", "héroe Grok bárbaro", "magia", "dormir"]for command in commands: words = command.split() try: if words[0] == "pelea": game.start_battle() elif words[0] == "héroe": game.create_hero(words[1], words[2]) print(f"El héroe es {words[1]}") else: game.recover(words[0]) except GameError as e: print(f"No se puede: {e}")No se puede: Crea primero un héroeNo se puede: No existe esa raza: dragónEl héroe es GrokNo se puede: Grok no domina la magia curativaNo se puede: Elección desconocida «dormir»: ¿descanso o magia?const game = new Game(makeRandom(12));const commands = ["pelea", "héroe Grok dragón", "héroe Grok bárbaro", "magia", "dormir"];for (const command of commands) { const words = command.split(" "); try { if (words[0] === "pelea") { game.startBattle(); } else if (words[0] === "héroe") { game.createHero(words[1], words[2]); console.log(`El héroe es ${words[1]}`); } else { game.recover(words[0]); } } catch (e) { if (!(e instanceof GameError)) { throw e; } console.log(`No se puede: ${e.message}`); }}No se puede: Crea primero un héroeNo se puede: No existe esa raza: dragónEl héroe es GrokNo se puede: Grok no domina la magia curativaNo se puede: Elección desconocida «dormir»: ¿descanso o magia?¿Por qué una excepción propia y no la incorporada IllegalArgumentException? Porque catch (GameException e) captura solo los rechazos según las reglas. Si el juego tiene un error y lanza NullPointerException, no se esconderá como «No se puede». Detendrá el programa, y lo verás.
Pruébalo
Encuentra el error
El sexto monstruo debería recibir «La fila está llena». En cambio, el programa se detiene con ArrayIndexOutOfBoundsException. ¿En qué línea está el error?
Elige la línea con el error.
count vale 5, y 5 > monsters.length es false. La comprobación lo deja pasar, y el sexto se escribe en monsters[5], cuando el último lugar es monsters[4]. El array está lleno cuando count se iguala a su longitud: count == monsters.length.Pruébalo
¿Qué falta?
¿De qué clase hereda GameException?
class GameException extends {
public GameException(String message) {
super(message);
}
}RuntimeException: eso hace que la excepción sea no comprobada, y los métodos de Game no tienen que declararla con throws. Con Exception cada llamador tendría que capturarla o declararla, y los rechazos según las reglas son esperados y se capturan en un solo lugar.Ejercicio
Juega y amplía
Resuelve estos ejercicios en tu editor. Empieza con las clases de la lección.
1. Un menú. Los comandos llegan por la entrada, uno por línea, hasta «salir»:
- «héroe Nombre raza» crea el héroe;
- «monstruo chamán» (o «rey», «carnicero») añade un monstruo ya hecho, y «monstruo Nombre vida fuerza» añade uno tuyo: una clase
Beastde la raza «bestia»; - «azar» añade un monstruo aleatorio;
- «pelea» empieza la siguiente pelea, y «descanso» y «magia» son la elección después de ella.
Cada rechazo es una GameException que el menú captura, diciendo «No se puede: …». La semilla es 12.
héroe Morena elfohéroe Morena brujamonstruo Lobo 50 9monstruo dragónmonstruo chamánazarpeleamagiapeleadescansopeleasalirNo se puede: No existe esa raza: elfoEl héroe es MorenaEn la fila: LoboNo se puede: No existe ese monstruo: dragónEn la fila: Chamán CaídoEn la fila: Chamán CaídoLobo es derrotado en 4 rondas. Morena: vida 68/95Morena invoca a los elementos: vida 83/95Chamán Caído es derrotado en 4 rondas. Morena: vida 47/95Morena llega al nivel 2: vida 105, fuerza 19Morena descansa 5 turnos: vida 105/105Chamán Caído es derrotado en 4 rondas. Morena: vida 69/1052. Progreso. Añade a Game un método showProgress() que muestre el nivel del héroe y cuánta experiencia tiene de la que necesita para el siguiente nivel. Ardan el paladín pasa por dos chamanes y el Rey Esqueleto. Después de cada pelea muestra su progreso y se cura con magia.
Chamán Caído es derrotado en 5 rondas. Ardan: vida 72/120Nivel 1, experiencia 60/100Ardan se cura: vida 92/120Chamán Caído es derrotado en 5 rondas. Ardan: vida 44/120Ardan llega al nivel 2: vida 130, fuerza 17Nivel 2, experiencia 20/200Ardan se cura: vida 130/130Rey Esqueleto es derrotado en 8 rondas. Ardan: vida 18/130Nivel 2, experiencia 110/200Ardan se cura: vida 38/1303. Un jefe. Una clase Diablo, subclase de Monster: 160 de vida, 14 de fuerza. Uno de cada tres golpes suyos es doble. Un método canChallenge(Character hero) dice si el héroe está al menos en el nivel 2. Si no, startBattle lanza GameException, y Diablo se queda en la fila. Ejecútalo una vez contra Grok en el nivel 1, y luego en un juego nuevo después del chamán y del rey.
No se puede: Diablo solo pelea contra héroes de nivel 2 o másChamán Caído es derrotado en 3 rondas. Grok: vida 90/110Grok descansa 5 turnos: vida 110/110Rey Esqueleto es derrotado en 6 rondas. Grok: vida 45/110Grok llega al nivel 2: vida 120, fuerza 23Grok descansa 5 turnos: vida 120/120Diablo es derrotado en 7 rondas. Grok: vida 24/120Grok llega al nivel 3: vida 130, fuerza 26Grok descansa 5 turnos: vida 130/130Sigue por tu cuenta
El ejercicio antiguo proponía más ideas. Algunas necesitan una herramienta que llega más adelante:
- Habilidades con niveles. Una clase
Skillcon un nombre y un nivel,useSkill(Character target)yupgradeSkill(). Esto ya puedes hacerlo, con un array de habilidades en el héroe. - Inventario. Una clase
Itemcon un nombre y un efecto,applyEffect(Character target), y unInventorydonde se añaden y se quitan objetos. Con un array es incómodo. La listaArrayListllega en el módulo Colecciones. - Jefes con requisitos. Un jefe que pide ciertos objetos y habilidades antes de aceptar la pelea, y suelta botín después de una victoria. Necesita el inventario de arriba.
- Pantallas. Menú, mapa, inventario: cada pantalla es un objeto con
show(),hide()yupdate(), y las pantallas abiertas están en una pila, para que «volver» funcione. La pila llega en el módulo Colecciones. - Guardar y cargar. El héroe, el nivel y la mejor puntuación se escriben en un archivo y se leen en la siguiente ejecución. Los archivos llegan en el módulo Texto y archivos.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Interfaces · Comprobaciones de tipo · Excepciones · Lanzar excepciones y crear las tuyas
- Ejercicios sobre el tema: Hagamos un juego