RPG 3: das ganze Spiel
Kurz gesagt: Das ganze Rollenspiel: Eine Klasse Game führt den Helden durch Kämpfe, Rast, Stufen und Erfahrung, und ein falscher Zug löst GameError aus, den das Spiel abfängt, um weiterzumachen.
Problem
Von einem Kampf zu einem Spiel
Die Helden und die Monster kämpfen schon. Aber ein Spiel ist mehr als ein Kampf: Du erschaffst einen Helden, stellst die Monster auf, gegen die er antritt, und er geht sie nacheinander durch. Zwischen den Kämpfen ruht er sich aus oder heilt sich mit Magie.
Wenn das alles in dem Hauptprogramm steht, liegen dort der Held, eine Liste mit Monstern, die Nummer des nächsten, Zähler und Prüfungen. Und wer hält den Spieler davon ab, einen Kampf ohne Helden zu starten oder von einem Barbaren Magie zu verlangen?
Der Zustand des Spiels und seine Regeln müssen an einem Ort stehen: in einer Klasse Game.
Die Idee
Die Klasse Game
Der Held und die Monster sind der Zustand des Spiels. Die Methoden von Game sind die Regeln: was der Spieler tun kann und was danach passiert. Die erste ist, einen Helden zu erstellen:
private Character hero;private Monster[] Monster = 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("paladin")) { hero = new Paladin(name); } else if (race.equals("barbar")) { hero = new Barbarian(name); } else if (race.equals("hexe")) { hero = new Witch(name); } else { throw new GameException("Dieses Volk gibt es nicht: " + race); }}private Character hero;private Monster[] Monster = 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 == "paladin") { hero = new Paladin(name); } else if (race == "barbar") { hero = new Barbarian(name); } else if (race == "hexe") { hero = new Witch(name); } else { throw new GameException("Dieses Volk gibt es nicht: " + 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 == "paladin": self._hero = Paladin(name) elif race == "barbar": self._hero = Barbarian(name) elif race == "hexe": self._hero = Witch(name) else: raise GameError(f"Dieses Volk gibt es nicht: {race}")#hero = null;#monsters = new Array(5);#count = 0;#next = 0;#random;
constructor(random) { this.#random = random;}
createHero(name, race) { if (race === "paladin") { this.#hero = new Paladin(name); } else if (race === "barbar") { this.#hero = new Barbarian(name); } else if (race === "hexe") { this.#hero = new Witch(name); } else { throw new GameError(`Dieses Volk gibt es nicht: ${race}`); }}Random() ohne Startwert, und in den Beispielen ist es Random(12), damit die Ausgabe immer gleich ist.Die Felder beginnen mit _: Sie gehören zum Spiel und werden von außen nicht angefasst. [None] * 5 ist eine Liste mit 5 Plätzen, auf denen noch nichts liegt (None).Ein unbekanntes Volk ist kein Grund, irgendeinen Helden zu erschaffen. Die Methode wirft GameError: unsere eigene Exception für jeden Zug, den die Regeln nicht erlauben. Sie bekommt weiter unten eine eigene Karte.
Die Idee
Eine Reihe von Monstern
Die Monster warten in einer Schlange: Das zuerst hinzugefügte ist der erste Kampf. Hier ist die Schlange eine Liste und zwei Indizes:
public void addMonster(Monster monster) { if (count == Monster.length) { throw new GameException("Die Reihe ist voll: höchstens " + Monster.length + " Monster"); } Monster[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 == Monster.Length) { throw new GameException("Die Reihe ist voll: höchstens " + Monster.Length + " Monster"); } Monster[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"Die Reihe ist voll: höchstens {len(self._monsters)} Monster") 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(`Die Reihe ist voll: höchstens ${this.#monsters.length} Monster`); } 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;}_countist die Anzahl der bisher hinzugefügten Monster. Das neue kommt nachself._monsters[self._count]._nextist das Monster, das als Nächstes kämpft. Nach jedem Kampf steigt es um 1, und wenn es_counterreicht, ist die Reihe zu Ende.
add_monster wirft GameError, bevor es außerhalb der Liste schreibt. Eine Liste in Python kann auch mit append wachsen, aber hier wächst sie absichtlich nicht: Die Regel des Spiels lautet „höchstens 5“.create_random_monster ist der Bonus aus der alten Übung: ein zufällig gewähltes Monster. randrange(3) liefert 0, 1 oder 2.
Genauer, für Neugierige
-
Die alte Übung hielt die Monster in einer echten Warteschlange. In Python ist das eine
deque: Sie merkt sich selbst, welches das erste ist, und sie wird nie voll. Sie kommt im Modul Collections: Dort entfallen_countund_next, und die Reihe wächst, so weit sie muss. - Die Monster vor
_nextbleiben in der Liste. Sie schaden nicht: Niemand liest sie mehr.
Die Idee
Stufen und Erfahrung
Nach jedem Sieg bekommt der Held Erfahrung in Höhe des Lebens des Monsters. Auf Stufe 1 braucht er 100 Erfahrung bis Stufe 2, auf Stufe 2 braucht er 200 bis Stufe 3 und so weiter. Eine neue Stufe gibt 10 Leben mehr, 3 Kraft mehr und volles Leben:
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;}Die Methoden stehen in Character. Erfahrung über der Schwelle bleibt für die nächste Stufe. Die while-Schleife ist für den Fall, dass ein Sieg für zwei Stufen reicht.
Die Felder _max_health, _level und _experience sind neu in Character. Mit _max_health hebt Heilen das Leben nicht mehr über das Maximum. Von außen werden sie über die Properties max_health, level und experience gelesen, wie die Felder aus dem ersten Teil.
Probier es aus
Auf welcher Stufe steht der Barbar?
Grok beginnt auf Stufe 1 mit Kraft 20. Er gewinnt gegen den Schamanen (60 Erfahrung) und gegen den König (90 Erfahrung).
Was gibt das Programm aus? Stufe, Erfahrung, Kraft.
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_health
grok = Barbarian("Grok")grok.gain_experience(60)grok.gain_experience(90)print(grok.level, grok.experience, grok.power)60 + 90 = 150 Erfahrung. Auf Stufe 1 liegt die Schwelle bei 100: Grok steigt auf Stufe 2 auf, und 50 bleiben übrig. Stufe 3 kostet 200. Die Kraft ist 20 + 3 = 23.
Sieh es dir an
Die Schlacht und die Rast
start_battle prüft zuerst, ob der Kampf möglich ist. Dann nimmt es das nächste Monster und lässt den Kampf auf Leben und Tod aus dem zweiten Teil laufen. Es gibt zurück, ob der Held überlebt hat:
public boolean startBattle() { if (hero == null) { throw new GameException("Erstelle zuerst einen Helden"); } if (!hasMonsters()) { throw new GameException("Keine Monster in der Reihe"); } Monster monster = Monster[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() + " fällt in Runde " + rounds + ". Sieger: " + monster.getName()); return false; } System.out.println(monster.getName() + " wird in " + rounds + " Runden besiegt. " + hero.getName() + ": Leben " + hero.getHealth() + "/" + hero.getMaxHealth()); if (hero.gainExperience(monster.getReward())) { System.out.println(hero.getName() + " erreicht Stufe " + hero.getLevel() + ": Leben " + hero.getMaxHealth() + ", Kraft " + hero.getPower()); } return true;}public bool StartBattle(){ if (hero == null) { throw new GameException("Erstelle zuerst einen Helden"); } if (!HasMonsters()) { throw new GameException("Keine Monster in der Reihe"); } Monster monster = Monster[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 + " fällt in Runde " + rounds + ". Sieger: " + monster.Name); return false; } Console.WriteLine(monster.Name + " wird in " + rounds + " Runden besiegt. " + hero.Name + ": Leben " + hero.Health + "/" + hero.MaxHealth); if (hero.GainExperience(monster.Reward)) { Console.WriteLine(hero.Name + " erreicht Stufe " + hero.Level + ": Leben " + hero.MaxHealth + ", Kraft " + hero.Power); } return true;}def start_battle(self): if self._hero is None: raise GameError("Erstelle zuerst einen Helden") if not self.has_monsters(): raise GameError("Keine Monster in der Reihe") 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} fällt in Runde {rounds}. Sieger: {monster.name}") return False print(f"{monster.name} wird in {rounds} Runden besiegt. {hero.name}: Leben {hero.health}/{hero.max_health}") if hero.gain_experience(monster.reward): print(f"{hero.name} erreicht Stufe {hero.level}: Leben {hero.max_health}, Kraft {hero.power}") return TruestartBattle() { if (this.#hero === null) { throw new GameError("Erstelle zuerst einen Helden"); } if (!this.hasMonsters()) { throw new GameError("Keine Monster in der Reihe"); } 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} fällt in Runde ${rounds}. Sieger: ${monster.name}`); return false; } console.log(`${monster.name} wird in ${rounds} Runden besiegt. ${hero.name}: Leben ${hero.health}/${hero.maxHealth}`); if (hero.gainExperience(monster.reward)) { console.log(`${hero.name} erreicht Stufe ${hero.level}: Leben ${hero.maxHealth}, Kraft ${hero.power}`); } return true;}recover ist die Wahl nach dem Kampf. Nur ein Held, der von Healable erbt (isinstance prüft das), kann Magie nutzen. Die Rast dauert 5 Züge zu je 10 Leben:
public void recover(String way) { if (way.equals("ruhe")) { for (int turn = 0; turn < 5; turn++) { hero.restore(10); } System.out.println(hero.getName() + " ruht 5 Züge lang: Leben " + hero.getHealth() + "/" + hero.getMaxHealth()); } else if (way.equals("magie")) { if (!(hero instanceof IHealable)) { throw new GameException(hero.getName() + " beherrscht keine Heilmagie"); } System.out.println(hero.useAbility()); } else { throw new GameException("Unbekannte Wahl „" + way + "“: ruhe oder magie?"); }}public void Recover(string way){ if (way == "ruhe") { for (int turn = 0; turn < 5; turn++) { hero.Restore(10); } Console.WriteLine(hero.Name + " ruht 5 Züge lang: Leben " + hero.Health + "/" + hero.MaxHealth); } else if (way == "magie") { if (!(hero is IHealable)) { throw new GameException(hero.Name + " beherrscht keine Heilmagie"); } Console.WriteLine(hero.UseAbility()); } else { throw new GameException("Unbekannte Wahl „" + way + "“: ruhe oder magie?"); }}def recover(self, way): hero = self._hero if way == "ruhe": for _ in range(5): hero.restore(10) print(f"{hero.name} ruht 5 Züge lang: Leben {hero.health}/{hero.max_health}") elif way == "magie": if not isinstance(hero, Healable): raise GameError(f"{hero.name} beherrscht keine Heilmagie") print(hero.use_ability()) else: raise GameError(f"Unbekannte Wahl „{way}“: ruhe oder magie?")recover(way) { const hero = this.#hero; if (way === "ruhe") { for (let turn = 0; turn < 5; turn++) { hero.restore(10); } console.log(`${hero.name} ruht 5 Züge lang: Leben ${hero.health}/${hero.maxHealth}`); } else if (way === "magie") { if (typeof hero.heal !== "function") { throw new GameError(`${hero.name} beherrscht keine Heilmagie`); } console.log(hero.useAbility()); } else { throw new GameError(`Unbekannte Wahl „${way}“: ruhe oder magie?`); }}In der alten Übung wartete der Held 5 Sekunden. Hier zählt die Rast in Zügen, weil in einem rundenbasierten Spiel die Zeit in Zügen gemessen wird und die Prüfung des Spiels nicht warten soll.
Sieh es dir an
Das ganze Spiel
Grok der Barbar gegen den Schamanen und zwei zufällige Monster. Nach jedem Kampf ruht er sich aus. Fällt er, ist das Spiel aus:
Game game = new Game(new Random(12));game.createHero("Grok", "barbar");game.addMonster(new FallenShaman());game.addMonster(game.createRandomMonster());game.addMonster(game.createRandomMonster());while (game.hasMonsters()) { if (!game.startBattle()) { System.out.println("Spiel vorbei"); break; } game.recover("ruhe");}Gefallener Schamane wird in 3 Runden besiegt. Grok: Leben 90/110Grok ruht 5 Züge lang: Leben 110/110Gefallener Schamane wird in 3 Runden besiegt. Grok: Leben 90/110Grok erreicht Stufe 2: Leben 120, Kraft 23Grok ruht 5 Züge lang: Leben 120/120Schlächter wird in 6 Runden besiegt. Grok: Leben 70/120Grok ruht 5 Züge lang: Leben 120/120Game game = new Game(new Random(12));game.CreateHero("Grok", "barbar");game.AddMonster(new FallenShaman());game.AddMonster(game.CreateRandomMonster());game.AddMonster(game.CreateRandomMonster());while (game.HasMonsters()){ if (!game.StartBattle()) { Console.WriteLine("Spiel vorbei"); break; } game.Recover("ruhe");}Gefallener Schamane wird in 3 Runden besiegt. Grok: Leben 90/110Grok ruht 5 Züge lang: Leben 110/110Schlächter wird in 7 Runden besiegt. Grok: Leben 48/110Grok erreicht Stufe 2: Leben 120, Kraft 23Grok ruht 5 Züge lang: Leben 120/120Skelettkönig wird in 5 Runden besiegt. Grok: Leben 68/120Grok ruht 5 Züge lang: Leben 118/120game = Game(Random(12))game.create_hero("Grok", "barbar")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("Spiel vorbei") break game.recover("ruhe")Gefallener Schamane wird in 3 Runden besiegt. Grok: Leben 90/110Grok ruht 5 Züge lang: Leben 110/110Skelettkönig wird in 6 Runden besiegt. Grok: Leben 45/110Grok erreicht Stufe 2: Leben 120, Kraft 23Grok ruht 5 Züge lang: Leben 120/120Skelettkönig wird in 5 Runden besiegt. Grok: Leben 68/120Grok ruht 5 Züge lang: Leben 118/120const game = new Game(makeRandom(12));game.createHero("Grok", "barbar");game.addMonster(new FallenShaman());game.addMonster(game.createRandomMonster());game.addMonster(game.createRandomMonster());while (game.hasMonsters()) { if (!game.startBattle()) { console.log("Spiel vorbei"); break; } game.recover("ruhe");}Gefallener Schamane wird in 3 Runden besiegt. Grok: Leben 90/110Grok ruht 5 Züge lang: Leben 110/110Skelettkönig wird in 6 Runden besiegt. Grok: Leben 45/110Grok erreicht Stufe 2: Leben 120, Kraft 23Grok ruht 5 Züge lang: Leben 120/120Skelettkönig wird in 5 Runden besiegt. Grok: Leben 68/120Grok ruht 5 Züge lang: Leben 118/120game = Game(Random(12))game.create_hero("Grok", "barbar")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("Spiel vorbei") break game.recover("ruhe")Schritt für Schritt, in Worten
- Das Spiel bekommt einen Generator mit Startwert 12. Der Held ist Grok, und die Liste hat 5 Plätze, noch leer.
- Jedes add_monster() schreibt nach self._monsters[self._count] und erhöht self._count. Das zweite und das dritte sind zufällig: Skelettkönig und Skelettkönig.
- self._next ist 0: Es kommt Gefallener Schamane. Es wird in 3 Runden besiegt, und Grok hat noch 90/110.
- self._next wird 1. Die Rast sind 5 Züge zu je 10 Leben: 110/110.
- self._next ist 1: Es kommt Skelettkönig. Es wird in 6 Runden besiegt, und Grok hat noch 45/110. Die Erfahrung reicht für Stufe 2: Leben 120, Kraft 23.
- self._next wird 2. Die Rast sind 5 Züge zu je 10 Leben: 120/120.
- self._next ist 2: Es kommt Skelettkönig. Es wird in 5 Runden besiegt, und Grok hat noch 68/120.
- self._next wird 3. Die Rast sind 5 Züge zu je 10 Leben: 118/120.
- self._next ist 3, genauso wie self._count: has_monsters() gibt False zurück, und die Schleife endet.
Die Idee
Ein falscher Zug: GameError
Der Spieler kann einen Kampf ohne Helden verlangen, ein Volk, das es nicht gibt, oder Magie von einem Barbaren. Das ist kein Fehler im Programm. Es ist ein Zug, den die Regeln nicht erlauben. Die Methoden von Game werfen dafür ihre eigene Exception:
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"; }}Error, wie bei den meisten eingebauten: ValueError, TypeError, IndexError.Das Spiel fängt nur diese eine ab, sagt dem Spieler, warum der Zug nicht erlaubt ist, und macht mit dem nächsten Zug weiter:
Game game = new Game(new Random(12));String[] commands = { "kampf", "held Grok drache", "held Grok barbar", "magie", "schlaf" };for (String command : commands) { String[] words = command.split(" "); try { if (words[0].equals("kampf")) { game.startBattle(); } else if (words[0].equals("held")) { game.createHero(words[1], words[2]); System.out.println("Der Held ist " + words[1]); } else { game.recover(words[0]); } } catch (GameException e) { System.out.println("Nicht erlaubt: " + e.getMessage()); }}Nicht erlaubt: Erstelle zuerst einen HeldenNicht erlaubt: Dieses Volk gibt es nicht: dracheDer Held ist GrokNicht erlaubt: Grok beherrscht keine HeilmagieNicht erlaubt: Unbekannte Wahl „schlaf“: ruhe oder magie?Game game = new Game(new Random(12));string[] commands = { "kampf", "held Grok drache", "held Grok barbar", "magie", "schlaf" };foreach (string command in commands){ string[] words = command.Split(' '); try { if (words[0] == "kampf") { game.StartBattle(); } else if (words[0] == "held") { game.CreateHero(words[1], words[2]); Console.WriteLine("Der Held ist " + words[1]); } else { game.Recover(words[0]); } } catch (GameException e) { Console.WriteLine("Nicht erlaubt: " + e.Message); }}Nicht erlaubt: Erstelle zuerst einen HeldenNicht erlaubt: Dieses Volk gibt es nicht: dracheDer Held ist GrokNicht erlaubt: Grok beherrscht keine HeilmagieNicht erlaubt: Unbekannte Wahl „schlaf“: ruhe oder magie?game = Game(Random(12))commands = ["kampf", "held Grok drache", "held Grok barbar", "magie", "schlaf"]for command in commands: words = command.split() try: if words[0] == "kampf": game.start_battle() elif words[0] == "held": game.create_hero(words[1], words[2]) print(f"Der Held ist {words[1]}") else: game.recover(words[0]) except GameError as e: print(f"Nicht erlaubt: {e}")Nicht erlaubt: Erstelle zuerst einen HeldenNicht erlaubt: Dieses Volk gibt es nicht: dracheDer Held ist GrokNicht erlaubt: Grok beherrscht keine HeilmagieNicht erlaubt: Unbekannte Wahl „schlaf“: ruhe oder magie?const game = new Game(makeRandom(12));const commands = ["kampf", "held Grok drache", "held Grok barbar", "magie", "schlaf"];for (const command of commands) { const words = command.split(" "); try { if (words[0] === "kampf") { game.startBattle(); } else if (words[0] === "held") { game.createHero(words[1], words[2]); console.log(`Der Held ist ${words[1]}`); } else { game.recover(words[0]); } } catch (e) { if (!(e instanceof GameError)) { throw e; } console.log(`Nicht erlaubt: ${e.message}`); }}Nicht erlaubt: Erstelle zuerst einen HeldenNicht erlaubt: Dieses Volk gibt es nicht: dracheDer Held ist GrokNicht erlaubt: Grok beherrscht keine HeilmagieNicht erlaubt: Unbekannte Wahl „schlaf“: ruhe oder magie?Warum eine eigene Exception und nicht die eingebaute ValueError? Weil except GameError nur die Ablehnungen nach den Regeln abfängt. Hat das Spiel einen Fehler und wirft AttributeError, versteckt sich dieser nicht als „Nicht erlaubt“. Er hält das Programm an, und du siehst ihn. Zum Beispiel recover, bevor es einen Helden gibt: self._hero ist None, und hero.restore(10) ergibt AttributeError: 'NoneType' object has no attribute 'restore'.
Probier es aus
Finde den Fehler
Das sechste Monster sollte „Die Reihe ist voll“ bekommen. Stattdessen bricht das Programm mit IndexError ab. In welcher Zeile steckt der Fehler?
Wähle die Zeile mit dem Fehler.
self._count gleich 5, und 5 > len(self._monsters) ist False. Die Prüfung lässt es durch, und das sechste wird nach self._monsters[5] geschrieben, obwohl der letzte Platz self._monsters[4] ist: IndexError: list assignment index out of range. Die Liste ist voll, wenn self._count gleich ihrer Länge wird: self._count == len(self._monsters).Probier es aus
Was fehlt?
Von welcher Klasse erbt GameError?
class GameError():
passException, die Elternklasse eigener Exceptions in Python. Ohne eigenes __init__ nutzt GameError den Konstruktor von Exception: Er behält die Meldung, und str(e) gibt sie zurück.Übung
Spielen und erweitern
Löse diese Aufgaben in deinem Editor. Beginne mit den Klassen aus der Lektion.
1. Ein Menü. Die Befehle kommen aus der Eingabe, einer pro Zeile, bis „ende“:
- „held Name Volk“ erstellt den Helden;
- „monster schamane“ (oder „könig“, „schlächter“) fügt ein fertiges Monster hinzu, und „monster Name Leben Kraft“ fügt dein eigenes hinzu: eine Klasse
Beastmit dem Volk „Bestie“; - „zufall“ fügt ein zufälliges Monster hinzu;
- „kampf“ startet den nächsten Kampf, und „ruhe“ und „magie“ sind die Wahl danach.
Jede Ablehnung ist eine GameError, die das Menü abfängt, mit der Meldung „Nicht erlaubt: …“. Der Startwert ist 12. Die Zeilen werden mit input() gelesen.
held Morena Elfheld Morena hexemonster Wolf 50 9monster drachemonster schamanezufallkampfmagiekampfruhekampfendeNicht erlaubt: Dieses Volk gibt es nicht: ElfDer Held ist MorenaIn der Reihe: WolfNicht erlaubt: Dieses Monster gibt es nicht: dracheIn der Reihe: Gefallener SchamaneIn der Reihe: SkelettkönigWolf wird in 4 Runden besiegt. Morena: Leben 68/95Morena ruft die Elemente an: Leben 83/95Gefallener Schamane wird in 4 Runden besiegt. Morena: Leben 47/95Morena erreicht Stufe 2: Leben 105, Kraft 19Morena ruht 5 Züge lang: Leben 105/105Skelettkönig wird in 7 Runden besiegt. Morena: Leben 9/1052. Fortschritt. Füge Game eine Methode show_progress() hinzu, die die Stufe des Helden zeigt und wie viel Erfahrung er von der Menge hat, die er für die nächste Stufe braucht. Ardan der Paladin geht durch zwei Schamanen und den Skelettkönig. Nach jedem Kampf zeigt er seinen Fortschritt und heilt sich mit Magie.
Gefallener Schamane wird in 5 Runden besiegt. Ardan: Leben 72/120Stufe 1, Erfahrung 60/100Ardan heilt sich: Leben 92/120Gefallener Schamane wird in 5 Runden besiegt. Ardan: Leben 44/120Ardan erreicht Stufe 2: Leben 130, Kraft 17Stufe 2, Erfahrung 20/200Ardan heilt sich: Leben 130/130Skelettkönig wird in 8 Runden besiegt. Ardan: Leben 18/130Stufe 2, Erfahrung 110/200Ardan heilt sich: Leben 38/1303. Ein Boss. Eine Klasse Diablo, eine Unterklasse von Monster: 160 Leben, 14 Kraft. Jeder dritte Treffer von ihm zählt doppelt. Eine Methode can_challenge(hero) sagt, ob der Held mindestens auf Stufe 2 ist. Wenn nicht, wirft start_battle GameError, und Diablo bleibt in der Reihe. Lass ihn einmal gegen Grok auf Stufe 1 laufen, dann in einem neuen Spiel nach dem Schamanen und dem König.
Nicht erlaubt: Diablo kämpft nur gegen Helden ab Stufe 2Gefallener Schamane wird in 3 Runden besiegt. Grok: Leben 90/110Grok ruht 5 Züge lang: Leben 110/110Skelettkönig wird in 6 Runden besiegt. Grok: Leben 45/110Grok erreicht Stufe 2: Leben 120, Kraft 23Grok ruht 5 Züge lang: Leben 120/120Diablo wird in 7 Runden besiegt. Grok: Leben 24/120Grok erreicht Stufe 3: Leben 130, Kraft 26Grok ruht 5 Züge lang: Leben 130/130Mach allein weiter
Die alte Übung hatte weitere Ideen. Manche brauchen ein Werkzeug, das erst später kommt:
- Fertigkeiten mit Stufen. Eine Klasse
Skillmit einem Namen und einer Stufe,use_skill(target)undupgrade_skill(). Das kannst du schon jetzt, mit einer Liste von Fertigkeiten im Helden. - Inventar. Eine Klasse
Itemmit einem Namen und einer Wirkung,apply_effect(target), und einInventory, in dem Gegenstände hinzugefügt und entfernt werden. Hinzufügen geht schon jetzt: Eine Liste wächst mitappend. Ein Element aus der Mitte zu entfernen kommt im Modul Collections. - Bosse mit Voraussetzungen. Ein Boss, der bestimmte Gegenstände und Fertigkeiten verlangt, bevor er den Kampf annimmt, und nach einem Sieg Beute fallen lässt. Dafür brauchst du das Inventar von oben.
- Bildschirme. Menü, Karte, Inventar: Jeder Bildschirm ist ein Objekt mit
show(),hide()undupdate(), und die offenen Bildschirme liegen auf einem Stapel, damit „zurück“ funktioniert. Der Stapel kommt im Modul Collections. - Speichern und Laden. Der Held, die Stufe und das beste Ergebnis werden in eine Datei geschrieben und beim nächsten Start gelesen. Dateien kommen im Modul Text und Dateien.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Interfaces als abstrakte Klassen · Typprüfungen · Exceptions · Werfen und eigene Exceptions
- Aufgaben zum Thema: Wir bauen ein Spiel