cat posts/python-lernen-04-sammlungen.md
Python lernen (Teil 4): Sammlungen
Listen, Tupel, Dictionaries und Sets – damit beschreiben wir die Dungeon-Welt als Daten und geben unserem Abenteurer ein richtiges Inventar.
In Teil 3 haben wir einen
Game-Loop gebaut, der auf die Befehle des Spielers reagiert. Die möglichen Wege
und Ereignisse steckten dabei direkt in einer langen if-elif-Kette.
Bei zwei oder drei Räumen funktioniert das noch. Besteht unser Dungeon später aber aus dreißig Räumen, wird der Game-Loop schnell unübersichtlich.
Heute lernen wir deshalb Sammlungen kennen. Mit ihnen können wir mehrere Werte gemeinsam speichern und die Welt unseres Dungeons als Daten beschreiben.
Python bringt dafür verschiedene Typen von Sammlungen (Collections) mit:
- Listen
- Tupel
- Dictionaries
- Sets
Sie erfüllen unterschiedliche Aufgaben. Am Ende dieses Teils verwenden wir sie, um Räume, Ausgänge, Gegenstände und den Player State übersichtlich abzubilden.
Listen: geordnet und veränderbar
Eine Liste speichert mehrere Werte in einer bestimmten Reihenfolge. Listen werden mit eckigen Klammern geschrieben:
inventar = ["Fackel", "Brot", "Seil"]
Die einzelnen Werte heißen Elemente. Eine Liste kann beliebig viele Elemente enthalten oder auch leer sein:
leeres_inventar = []
Auf Elemente zugreifen
Jedes Element hat eine Position, den sogenannten Index. Die Zählung beginnt
bei 0:
inventar = ["Fackel", "Brot", "Seil"]
print(inventar[0])
print(inventar[1])
print(inventar[2])
Die Ausgabe lautet:
Fackel
Brot
Seil
Die Indizes dieser Liste sehen damit so aus:
| Index | Element |
|---|---|
0 |
"Fackel" |
1 |
"Brot" |
2 |
"Seil" |
Dass die Zählung bei 0 beginnt, ist anfangs ungewohnt, in der Programmierung
aber sehr verbreitet.
Mit negativen Indizes kannst Du vom Ende der Liste aus zugreifen:
print(inventar[-1])
Die Ausgabe lautet:
Seil
-1 bezeichnet das letzte Element, -2 das vorletzte und so weiter.
Listen verändern
Listen sind veränderbar (mutable). Mit append(...) fügst Du ein
einzelnes Element am Ende hinzu:
inventar = ["Fackel", "Brot"]
inventar.append("Schlüssel")
print(inventar)
Die Ausgabe lautet:
['Fackel', 'Brot', 'Schlüssel']
Mit remove(...) entfernst Du das erste Element mit einem bestimmten Wert:
inventar.remove("Brot")
print(inventar)
Danach enthält die Liste nur noch:
['Fackel', 'Schlüssel']
Möchtest Du eine Liste um mehrere Elemente erweitern, kannst Du extend(...)
verwenden:
inventar = ["Fackel"]
fund = ["Schlüssel", "Heiltrank"]
inventar.extend(fund)
print(inventar)
Die Ausgabe lautet:
['Fackel', 'Schlüssel', 'Heiltrank']
Der Unterschied zu append(...) ist wichtig. Folgendes:
inventar.append(fund)
fügt die komplette Liste fund als ein einziges Element hinzu:
['Fackel', ['Schlüssel', 'Heiltrank']]
Wir hätten dann eine Liste innerhalb unserer Liste.
Anzahl der Elemente bestimmen
Mit len(...) ermittelst Du die Länge einer Liste:
inventar = ["Fackel", "Brot", "Seil"]
print(len(inventar))
Die Ausgabe lautet:
3
Eine leere Liste besitzt entsprechend die Länge 0.
Prüfen, ob ein Element enthalten ist
Mit in kannst Du prüfen, ob ein bestimmter Wert in einer Liste vorkommt:
inventar = ["Fackel", "Brot", "Seil"]
print("Brot" in inventar)
print("Schlüssel" in inventar)
Die Ausgabe lautet:
True
False
Das Ergebnis lässt sich direkt in einer Bedingung verwenden:
if "Schlüssel" in inventar:
print("Du kannst die Tür öffnen.")
Eine Liste durchlaufen
Mit einer for-Schleife kannst Du jedes Element der Reihe nach verarbeiten:
inventar = ["Fackel", "Brot", "Seil"]
for gegenstand in inventar:
print(f"- {gegenstand}")
Die Ausgabe lautet:
- Fackel
- Brot
- Seil
Einen Index brauchst Du dafür nicht. Bei jedem Durchlauf verweist
gegenstand auf das nächste Element der Liste.
Tupel: geordnet und unveränderbar
Ein Tupel ähnelt einer Liste: Es ist geordnet und kann mehrere Werte enthalten. Es ist jedoch unveränderbar (immutable): Die Elemente eines Tupels lassen sich nach seiner Erstellung nicht mehr ersetzen, hinzufügen oder entfernen.
Tupel werden üblicherweise mit runden Klammern geschrieben:
position = (3, 7)
Auf die Elemente greifst Du wie bei einer Liste über ihren Index zu:
print(position[0])
print(position[1])
Die Ausgabe lautet:
3
7
Eine Position aus einer X- und einer Y-Koordinate ist ein typischer Anwendungsfall: Beide Werte gehören zusammen und werden als Einheit behandelt.
Du kannst sie auch direkt auf zwei Variablen verteilen:
position = (3, 7)
x, y = position
print(f"X: {x}, Y: {y}")
Das nennt sich Unpacking.
Tupel lassen sich nicht verändern
Der folgende Versuch führt zu einem TypeError:
position = (3, 7)
position[0] = 9
Python erlaubt es nicht, einem Element des Tupels nachträglich einen anderen Wert zuzuweisen.
Tupel eignen sich deshalb gut für zusammengehörige Werte, deren Zusammensetzung nach dem Erstellen gleich bleiben soll.
Ein Tupel mit nur einem Element
Bei einem Tupel mit nur einem Element ist das Komma entscheidend:
ein_element = ("Schlüssel",)
Ohne das Komma entsteht lediglich ein String in Klammern:
kein_tupel = ("Schlüssel")
print(type(kein_tupel))
Die Ausgabe lautet:
<class 'str'>
Technisch ist es also das Komma, das das Tupel erzeugt, nicht die runden Klammern allein.
Dictionaries: Werte über Schlüssel erreichen
Ein Dictionary, kurz dict, ordnet Schlüsseln bestimmte Werte zu.
Während Du bei einer Liste über einen numerischen Index auf ein Element zugreifst, verwendest Du bei einem Dictionary einen Schlüssel:
raum = {
"name": "Halle",
"beschreibung": "Eine dunkle Halle mit moderigem Geruch.",
"besucht": False,
}
Ein Dictionary wird mit geschweiften Klammern geschrieben. Jeder Eintrag besteht aus einem Schlüssel und dem zugehörigen Wert:
"beschreibung": "Eine dunkle Halle mit moderigem Geruch."
Der Doppelpunkt trennt Schlüssel und Wert voneinander.
Dictionaries behalten die Reihenfolge bei, in der ihre Einträge eingefügt wurden. Anders als bei einer Liste greifst Du aber nicht über diese Position auf einen Wert zu, sondern über seinen Schlüssel.
Auf einen Wert zugreifen
Den Wert eines bestimmten Schlüssels erhältst Du mit eckigen Klammern:
print(raum["name"])
print(raum["beschreibung"])
Die Ausgabe lautet:
Halle
Eine dunkle Halle mit moderigem Geruch.
Werte verändern und ergänzen
Dictionaries sind wie Listen mutable. Du kannst einen vorhandenen Wert verändern:
raum["besucht"] = True
Mit derselben Schreibweise lässt sich ein neuer Eintrag ergänzen:
raum["licht"] = "dunkel"
Existiert der Schlüssel bereits, wird sein Wert ersetzt. Existiert er noch nicht, wird ein neuer Eintrag angelegt.
Prüfen, ob ein Schlüssel existiert
Der Operator in prüft bei einem Dictionary dessen Schlüssel:
print("beschreibung" in raum)
print("monster" in raum)
Die Ausgabe lautet:
True
False
Das ist praktisch, wenn ein Eintrag nur in manchen Dictionaries vorhanden ist:
if "monster" in raum:
print("Hier wartet etwas auf Dich.")
Fehlende Schlüssel und .get(...)
Greifst Du mit eckigen Klammern auf einen nicht vorhandenen Schlüssel zu,
entsteht ein KeyError:
print(raum["monster"])
Mit .get(...) bekommst Du bei einem fehlenden Schlüssel stattdessen None:
monster = raum.get("monster")
print(monster)
Du kannst außerdem einen Default-Wert angeben:
monster = raum.get("monster", "Kein Monster")
print(monster)
Die Ausgabe lautet:
Kein Monster
Das ist nützlich, wenn ein fehlender Eintrag ein normaler Zustand ist und keinen Fehler darstellen soll.
Dictionaries durchlaufen
Eine for-Schleife über ein Dictionary durchläuft standardmäßig seine
Schlüssel:
for schluessel in raum:
print(schluessel)
Mit .values() erhältst Du stattdessen die Werte:
for wert in raum.values():
print(wert)
Und mit .items() bekommst Du bei jedem Durchlauf Schlüssel und Wert
gemeinsam:
for schluessel, wert in raum.items():
print(f"{schluessel}: {wert}")
Das Unpacking funktioniert hier genauso wie beim Tupel: Schlüssel und Wert werden direkt auf die beiden Variablen verteilt.
Verschachtelte Collections
Die Werte eines Dictionaries dürfen selbst wieder Listen, Tupel, Sets oder weitere Dictionaries sein:
raum = {
"name": "Halle",
"ausgaenge": {
"norden": "bibliothek",
"osten": "krypta",
},
"gegenstaende": ["Fackel", "Brot"],
}
raum["ausgaenge"] liefert hier ein weiteres Dictionary:
print(raum["ausgaenge"])
Um das Ziel des nördlichen Ausgangs zu erhalten, kombinieren wir zwei Zugriffe:
print(raum["ausgaenge"]["norden"])
Die Ausgabe lautet:
bibliothek
Genau solche verschachtelten Strukturen nutzen wir gleich, um die gesamte Dungeon-Welt zu beschreiben.
Sets: ungeordnet und ohne Duplikate
Ein Set ist eine ungeordnete Collection, in der jeder Wert höchstens einmal vorkommt:
besuchte_raeume = {"halle", "bibliothek", "halle"}
print(besuchte_raeume)
Obwohl "halle" zweimal angegeben wurde, ist der Wert im Set nur einmal
enthalten. Eine mögliche Ausgabe ist:
{'halle', 'bibliothek'}
Die Reihenfolge kann auch anders aussehen. Bei Sets gibt es keine garantierte Position der Elemente und deshalb auch keinen Zugriff über einen Index.
Werte hinzufügen
Mit .add(...) fügst Du einem Set einen Wert hinzu:
besuchte_raeume.add("krypta")
Fügst Du denselben Wert erneut hinzu, bleibt das Set unverändert:
besuchte_raeume.add("krypta")
"krypta" ist weiterhin nur einmal enthalten.
Mitgliedschaft prüfen
Auch bei Sets verwendest Du in:
if "krypta" in besuchte_raeume:
print("Du warst bereits in der Krypta.")
Sets eignen sich besonders gut für Fälle, in denen Dich vor allem interessiert, ob ein Wert bereits vorhanden ist, zum Beispiel:
- besuchte Räume
- freigeschaltete Fähigkeiten
- bekannte Zaubersprüche
- einmalige IDs
Ein leeres Set anlegen
Ein leeres Set wird mit set() erzeugt:
besuchte_raeume = set()
Leere geschweifte Klammern erzeugen dagegen ein Dictionary:
leere_sammlung = {}
print(type(leere_sammlung))
Die Ausgabe lautet:
<class 'dict'>
Das ist leicht zu verwechseln, weil nicht leere Sets ebenfalls mit geschweiften Klammern geschrieben werden.
Welche Collection passt wozu?
| Collection | Reihenfolge | Veränderbar | Duplikate | Zugriff |
|---|---|---|---|---|
| Liste | feste Reihenfolge | ja | ja | über Index |
| Tupel | feste Reihenfolge | nein | ja | über Index |
| Dictionary | Einfügereihenfolge | ja | Schlüssel eindeutig | über Schlüssel |
| Set | keine garantierte Reihenfolge | ja | nein | über Mitgliedschaft |
Für unseren Dungeon verwenden wir:
- ein Dictionary für den Player State
- eine Liste für das Inventar
- ein Dictionary für die Räume
- ein Dictionary für die Ausgänge jedes Raums
- eine Liste für die Gegenstände in einem Raum
- ein Set für bereits besuchte Räume
Damit haben wir für jeden Teil unseres States eine Collection, die zur jeweiligen Aufgabe passt.
Die Dungeon-Welt als Daten
Bisher steckte die Welt direkt in der Programmlogik:
if befehl == "norden":
print("Du stehst vor einer verschlossenen Tür.")
elif befehl == "osten":
print("Im Osten wartet ein Goblin.")
Jeder neue Raum und jeder neue Ausgang erforderte dadurch zusätzliche Bedingungen.
Stattdessen beschreiben wir die Welt nun in einem großen Dictionary:
raeume = {
"halle": {
"name": "Halle",
"beschreibung": "Du stehst in einer dunklen Halle mit moderigem Geruch.",
"ausgaenge": {
"norden": "bibliothek",
"osten": "krypta",
},
"gegenstaende": ["Fackel"],
},
"bibliothek": {
"name": "Bibliothek",
"beschreibung": "Staubige Regale voller zerfledderter Bücher umgeben Dich.",
"ausgaenge": {
"sueden": "halle",
},
"gegenstaende": ["Schlüssel"],
},
"krypta": {
"name": "Krypta",
"beschreibung": "Feuchtigkeit glänzt auf den Wänden der alten Krypta.",
"ausgaenge": {
"westen": "halle",
},
"gegenstaende": [],
},
}
Die Schlüssel "halle", "bibliothek" und "krypta" dienen als interne IDs
der Räume. Jeder Raum ist wiederum ein Dictionary mit vier Einträgen:
namebeschreibungausgaengegegenstaende
Die Ausgänge bilden eine Richtung auf die ID des jeweiligen Zielraums ab:
"ausgaenge": {
"norden": "bibliothek",
"osten": "krypta",
}
Der nördliche Ausgang der Halle führt also zum Raum mit der ID
"bibliothek", der östliche zur "krypta".
Damit müssen wir nicht mehr für jede mögliche Richtung einen eigenen
elif-Block schreiben.
Den Player State gruppieren
Auch der Player State besteht inzwischen aus mehreren zusammengehörigen Werten:
name = "Karl"
hp = 100
max_hp = 100
gold = 50
inventar = []
Diese Werte können wir ebenfalls in einem Dictionary zusammenfassen:
spieler = {
"name": "Karl",
"hp": 100,
"max_hp": 100,
"gold": 50,
"inventar": [],
}
Der Zugriff erfolgt nun über die jeweiligen Schlüssel:
print(spieler["name"])
print(spieler["hp"])
print(spieler["inventar"])
Damit ist auch im Code sofort erkennbar, welche Werte zum Player State gehören.
Der Dungeon, Schritt 4
Öffne dungeon.py und ersetze den bisherigen Inhalt durch folgenden Code:
# Dungeon – Teil 4: Collections und Welt als Daten
raeume = {
"halle": {
"name": "Halle",
"beschreibung": "Du stehst in einer dunklen Halle mit moderigem Geruch.",
"ausgaenge": {
"norden": "bibliothek",
"osten": "krypta",
},
"gegenstaende": ["Fackel"],
},
"bibliothek": {
"name": "Bibliothek",
"beschreibung": "Staubige Regale voller zerfledderter Bücher umgeben Dich.",
"ausgaenge": {
"sueden": "halle",
},
"gegenstaende": ["Schlüssel"],
},
"krypta": {
"name": "Krypta",
"beschreibung": "Feuchtigkeit glänzt auf den Wänden der alten Krypta.",
"ausgaenge": {
"westen": "halle",
},
"gegenstaende": [],
},
}
print("Du stehst vor dem rostigen Tor eines vergessenen Verlieses.")
print("Ein kalter Luftzug weht Dir entgegen.")
spieler = {
"name": input("Wie heißt Du, Abenteurer? ").strip(),
"hp": 100,
"max_hp": 100,
"gold": int(input("Wie viele Goldmünzen bringst Du mit? ")),
"inventar": [],
}
ort = "halle"
besuchte_raeume = set()
raum_anzeigen = True
print()
print(f"Willkommen, {spieler['name']}!")
print("Befehle: umsehen, nimm, inventar, status, ende")
while True:
raum = raeume[ort]
if raum_anzeigen:
besuchte_raeume.add(ort)
print()
print(raum["beschreibung"])
print("Ausgänge:", ", ".join(raum["ausgaenge"]))
raum_anzeigen = False
befehl = input("\n> ").strip().lower()
if befehl == "ende":
print("Du verlässt das Verlies. Bis zum nächsten Mal.")
break
elif befehl == "umsehen":
if raum["gegenstaende"]:
print("Hier liegt:", ", ".join(raum["gegenstaende"]))
else:
print("Hier liegt nichts Brauchbares.")
elif befehl == "nimm":
if raum["gegenstaende"]:
gefundene_gegenstaende = ", ".join(raum["gegenstaende"])
spieler["inventar"].extend(raum["gegenstaende"])
raum["gegenstaende"].clear()
print(f"Du nimmst: {gefundene_gegenstaende}")
else:
print("Hier gibt es nichts zu nehmen.")
elif befehl == "inventar":
if spieler["inventar"]:
print("Inventar:", ", ".join(spieler["inventar"]))
else:
print("Dein Inventar ist leer.")
elif befehl == "status":
print(
f"{spieler['name']} | "
f"HP: {spieler['hp']}/{spieler['max_hp']} | "
f"Gold: {spieler['gold']}"
)
print(f"Besuchte Räume: {len(besuchte_raeume)}")
print(f"Gegenstände: {len(spieler['inventar'])}")
elif befehl in raum["ausgaenge"]:
ort = raum["ausgaenge"][befehl]
raum_anzeigen = True
else:
print("Das geht hier nicht.")
print("Mögliche Ausgänge:", ", ".join(raum["ausgaenge"]))
print("Weitere Befehle: umsehen, nimm, inventar, status, ende")
Führe das Programm aus:
python dungeon.py
Ein möglicher Durchlauf sieht so aus:
Du stehst vor dem rostigen Tor eines vergessenen Verlieses.
Ein kalter Luftzug weht Dir entgegen.
Wie heißt Du, Abenteurer? Karl
Wie viele Goldmünzen bringst Du mit? 50
Willkommen, Karl!
Befehle: umsehen, nimm, inventar, status, ende
Du stehst in einer dunklen Halle mit moderigem Geruch.
Ausgänge: norden, osten
> umsehen
Hier liegt: Fackel
> nimm
Du nimmst: Fackel
> norden
Staubige Regale voller zerfledderter Bücher umgeben Dich.
Ausgänge: sueden
> nimm
Du nimmst: Schlüssel
> inventar
Inventar: Fackel, Schlüssel
> status
Karl | HP: 100/100 | Gold: 50
Besuchte Räume: 2
Gegenstände: 2
> ende
Du verlässt das Verlies. Bis zum nächsten Mal.
Was im neuen Game-Loop passiert
Der Player State steckt nun in einem Dictionary:
spieler = {
"name": input("Wie heißt Du, Abenteurer? ").strip(),
"hp": 100,
"max_hp": 100,
"gold": int(input("Wie viele Goldmünzen bringst Du mit? ")),
"inventar": [],
}
Das Inventar ist eine Liste innerhalb dieses Dictionaries:
spieler["inventar"]
Wir kombinieren also bereits zwei Collections: Ein Dictionary gruppiert den Player State, während eine Liste die Gegenstände des Spielers speichert.
Der aktuelle Raum steht weiterhin in der Variablen ort:
ort = "halle"
Mit diesem Wert holen wir das Dictionary des aktuellen Raums aus unserer Welt:
raum = raeume[ort]
Enthält ort den String "halle", entspricht das diesem Zugriff:
raum = raeume["halle"]
raum verweist anschließend auf das Dictionary der Halle. Wenn wir dessen
Inhalte verändern, verändern wir damit auch den entsprechenden Raum innerhalb
von raeume.
Bereits besuchte Räume speichern
Das Set besuchte_raeume beginnt leer:
besuchte_raeume = set()
Sobald ein Raum angezeigt wird, fügen wir seine ID hinzu:
besuchte_raeume.add(ort)
Betritt der Spieler denselben Raum später erneut, bleibt dessen ID trotzdem nur
einmal im Set. Mit len(...) erhalten wir deshalb die Anzahl der
unterschiedlichen Räume, die bereits besucht wurden.
Räume nur nach einem Wechsel anzeigen
Die bool-Variable raum_anzeigen bestimmt, ob die Beschreibung des aktuellen
Raums ausgegeben werden soll:
raum_anzeigen = True
Nach der Ausgabe setzen wir sie auf False:
raum_anzeigen = False
Dadurch erscheint die Raumbeschreibung nicht nach jedem einzelnen Befehl erneut.
Wechselt der Spieler in einen anderen Raum, setzen wir die Variable wieder auf
True:
ort = raum["ausgaenge"][befehl]
raum_anzeigen = True
Beim nächsten Durchlauf des Game-Loops wird das Dictionary des neuen Raums geladen und dessen Beschreibung ausgegeben.
Gültige Richtungen erkennen
Dieser Ausdruck prüft, ob der eingegebene Befehl einem Schlüssel im Ausgangs-Dictionary entspricht:
elif befehl in raum["ausgaenge"]:
Befindet sich der Spieler in der Halle, enthält dieses Dictionary folgende Einträge:
{
"norden": "bibliothek",
"osten": "krypta",
}
Die Befehle "norden" und "osten" sind dort deshalb gültig.
Das Ziel der gewählten Richtung erhalten wir direkt über den Schlüssel:
ort = raum["ausgaenge"][befehl]
Gibt der Spieler "norden" ein, entspricht das:
ort = raum["ausgaenge"]["norden"]
Der neue Wert von ort lautet anschließend "bibliothek".
Damit braucht die Bewegung durch den Dungeon keine eigene Bedingung mehr für jede einzelne Richtung. Dieselbe Zeile funktioniert für alle Ausgänge, die im jeweiligen Raum definiert sind.
Gegenstände aufnehmen
Die Gegenstände des aktuellen Raums liegen in einer Liste:
raum["gegenstaende"]
Mit extend(...) übernehmen wir alle Elemente dieser Liste in das Inventar:
spieler["inventar"].extend(raum["gegenstaende"])
Danach leeren wir die Liste im Raum:
raum["gegenstaende"].clear()
Die Gegenstände befinden sich jetzt im Inventar und liegen nicht mehr im Raum. Der Spieler kann sie deshalb nicht noch einmal aufnehmen.
Unser Befehl nimm nimmt momentan übrigens alle Gegenstände eines Raums auf
einmal. Eine Auswahl einzelner Gegenstände bauen wir später ein.
Listen als String ausgeben
Die Methode join(...) verbindet mehrere Strings zu einem gemeinsamen String:
", ".join(spieler["inventar"])
Aus
["Fackel", "Schlüssel"]
wird dadurch:
Fackel, Schlüssel
Der String vor .join(...) bestimmt, was zwischen die einzelnen Elemente
gesetzt wird. Hier ist das ein Komma gefolgt von einem Leerzeichen.
join(...) erwartet, dass die zu verbindenden Elemente Strings sind. Eine Liste
mit Zahlen funktioniert deshalb nicht direkt:
", ".join([1, 2, 3])
Das würde zu einem TypeError führen.
Daten und Logik trennen
Der wichtigste Schritt in diesem Teil ist weniger eine einzelne Methode als die neue Struktur unseres Programms.
Die Räume werden als Daten beschrieben:
raeume = {
...
}
Der Game-Loop enthält dagegen die Logik, die mit diesen Daten arbeitet:
while True:
...
Möchtest Du einen weiteren Raum hinzufügen, musst Du nicht mehr für jede seiner
Richtungen neue Bedingungen in den Game-Loop einbauen. Du ergänzt den Raum im
Dictionary raeume und verbindest ihn über seine Ausgänge mit der vorhandenen
Welt.
Unser Dungeon ist damit noch lange keine fertige Game Engine. Aber Welt und Programmlogik sind jetzt deutlich weniger eng miteinander verdrahtet. Das macht die nächsten Erweiterungen wesentlich angenehmer.
Stolperfallen
-
Beim Index bei
0zu zählen vergessen: Eine Liste mit drei Elementen besitzt die Indizes0,1und2. Ein Zugriff aufinventar[3]führt zu einemIndexError. -
Einen Wert mit
remove(...)entfernen, der nicht existiert: Findetremove(...)den angegebenen Wert nicht in der Liste, entsteht einValueError. -
Einen fehlenden Dictionary-Schlüssel verwenden: Ein Zugriff wie
raum["monster"]führt zu einemKeyError, wenn der Schlüssel nicht existiert. Mitraum.get("monster")erhältst Du stattdessenNone. -
Liste und Dictionary verwechseln: Auf Listen greifst Du mit numerischen Indizes zu, auf Dictionaries mit ihren Schlüsseln.
python
inventar[0]
raum["beschreibung"]
-
append(...)undextend(...)verwechseln:append(...)fügt einen Wert als neues Element hinzu.extend(...)übernimmt mehrere Elemente in die bestehende Liste. -
Ein leeres Set mit
{}anlegen:{}erzeugt ein leeres Dictionary. Für ein leeres Set benötigst Duset(). -
Von einer festen Set-Reihenfolge ausgehen: Sets besitzen keine verlässliche Reihenfolge für Deine Programmlogik. Verwende eine Liste oder ein Tupel, wenn die Position der Elemente wichtig ist.
-
Dictionary-Reihenfolge mit Sortierung verwechseln: Dictionaries behalten ihre Einfügereihenfolge bei, sortieren ihre Schlüssel aber nicht automatisch.
-
Zahlen direkt mit
join(...)verbinden:join(...)erwartet Strings.", ".join([1, 2, 3])führt deshalb zu einemTypeError. -
Umlaute in Befehlen übersehen: Im Code verwenden wir für die südliche Richtung bewusst
"sueden", damit sie ohne Umlaut eingegeben werden kann.
Übungen
1. Eine Schatzkammer ergänzen
Füge dem Verlies einen vierten Raum namens "schatzkammer" hinzu. Er soll von
der Bibliothek aus nach Norden erreichbar sein und einen "Goldhaufen"
enthalten.
Lösung
Ergänze das Dictionary `raeume` um folgenden Eintrag:"schatzkammer": {
"name": "Schatzkammer",
"beschreibung": "Gold und Edelsteine glitzern im Licht Deiner Fackel.",
"ausgaenge": {
"sueden": "bibliothek",
},
"gegenstaende": ["Goldhaufen"],
},
"ausgaenge": {
"sueden": "halle",
"norden": "schatzkammer",
},
2. Einen Startgegenstand hinzufügen
Gib dem Spieler zu Beginn ein Stück Brot. Das Inventar soll also nicht mehr leer starten.
Lösung
Ändere den Eintrag `inventar` im Player State:spieler = {
"name": input("Wie heißt Du, Abenteurer? ").strip(),
"hp": 100,
"max_hp": 100,
"gold": int(input("Wie viele Goldmünzen bringst Du mit? ")),
"inventar": ["Brot"],
}
3. Alle Ausgänge eines Raums ausgeben
Schreibe eine for-Schleife, die alle Richtungen und Zielräume der Halle
ausgibt.
Die Ausgabe soll ungefähr so aussehen:
norden -> bibliothek
osten -> krypta
Lösung
halle = raeume["halle"]
for richtung, ziel in halle["ausgaenge"].items():
print(f"{richtung} -> {ziel}")
Im nächsten Teil
Die Welt ist jetzt als Daten beschrieben. Der Game-Loop ist durch die vielen Befehle allerdings weiterhin recht lang.
In Teil 5 lernst Du deshalb Funktionen kennen. Damit zerlegen wir die Logik in benannte, wiederverwendbare Blöcke und machen den Game-Loop wieder übersichtlicher.
0 Kommentare
Noch keine Kommentare. Sei der/die Erste!
Anmelden um einen Kommentar zu hinterlassen.