Zum Inhalt springen

cat posts/python-lernen-03-entscheidungen-und-schleifen.md

Python lernen (Teil 3): Entscheidungen und Schleifen

Vergleiche, Bedingungen und Schleifen – mit einem Game-Loop, durch den der Dungeon auf Befehle reagiert und seinen State während des Spiels verändert.

Python lernen (Teil 3): Entscheidungen und Schleifen

Bis einschließlich Teil 2 lief unser Programm stur von oben nach unten. Es konnte Werte speichern und verändern, führte aber bei jedem Start dieselben Anweisungen in derselben Reihenfolge aus.

Heute lernt der Dungeon, Entscheidungen zu treffen und Anweisungen zu wiederholen. Dafür verwenden wir Bedingungen und Schleifen. Am Ende besitzt unser Text-Adventure einen Game-Loop, der auf Befehle wartet und unterschiedlich auf die Eingaben des Spielers reagiert.

Vergleiche liefern Booleans

Bevor Python eine Entscheidung treffen kann, braucht es eine Bedingung. Für den Anfang verwenden wir dafür vor allem Vergleiche. Ihr Ergebnis ist entweder True oder False.

Diese Booleans kennst Du bereits aus Teil 2 als Datentyp bool:

>>> 100 > 50
True
>>> 100 < 50
False
>>> 100 == 100
True

Für Vergleiche stehen verschiedene Operatoren zur Verfügung:

Operator Bedeutung Beispiel
== gleich hp == 100
!= ungleich hp != 0
> größer als gold > 50
< kleiner als hp < 25
>= größer oder gleich level >= 5
<= kleiner oder gleich hp <= 25

Das Ergebnis eines Vergleichs kannst Du wie jeden anderen Wert einer Variablen zuweisen:

hp = 20
ist_verletzt = hp < 100

print(ist_verletzt)

Die Ausgabe lautet:

True

= und == sind nicht das Gleiche

Ein einzelnes Gleichheitszeichen weist einem Namen einen Wert zu:

hp = 100

Ein doppeltes Gleichheitszeichen vergleicht dagegen zwei Werte:

hp == 100

Die beiden Ausdrücke haben also völlig unterschiedliche Aufgaben:

  • hp = 100 weist hp den Wert 100 zu.
  • hp == 100 prüft, ob hp den Wert 100 hat.

Die Verwechslung von = und == ist beim Einstieg schnell passiert und führt je nach Stelle zu einem Syntaxfehler oder schlicht zu anderem Code als beabsichtigt.

Bedingungen mit if

Mit if führst Du einen Code-Block nur dann aus, wenn eine Bedingung als wahr ausgewertet wird:

hp = 20

if hp <= 25:
    print("Vorsicht, Du bist schwer verletzt!")

Da 20 <= 25 den Wert True ergibt, wird die eingerückte Zeile ausgeführt.

Besitzt der Abenteurer dagegen 80 Lebenspunkte, ist der Vergleich False. Python überspringt den eingerückten Block:

hp = 80

if hp <= 25:
    print("Vorsicht, Du bist schwer verletzt!")

print(f"Du besitzt {hp} Lebenspunkte.")

Die Ausgabe lautet:

Du besitzt 80 Lebenspunkte.

Einrückungen bilden Code-Blöcke

Nach der if-Bedingung steht ein Doppelpunkt. Die Anweisungen, die zum if gehören, werden eingerückt:

if hp <= 25:
    print("Vorsicht!")
    print("Du solltest Dich heilen.")

print("Diese Zeile wird immer ausgeführt.")

Die beiden eingerückten print-Anweisungen bilden gemeinsam einen Code-Block. Sie werden nur ausgeführt, wenn die Bedingung erfüllt ist.

Die letzte Zeile ist nicht eingerückt und gehört deshalb nicht mehr zum if-Block. Sie wird unabhängig von der Bedingung ausgeführt.

Für Einrückungen werden in Python üblicherweise vier Leerzeichen verwendet. Die meisten Code-Editoren erledigen das automatisch, sobald Du nach dem Doppelpunkt Enter drückst.

Mehrere Fälle mit elif und else

Mit elif kannst Du weitere Bedingungen prüfen. Die Abkürzung steht für else if. Ein abschließendes else greift, wenn keine der vorherigen Bedingungen erfüllt war:

hp = 20

if hp <= 0:
    print("Du bist besiegt.")
elif hp <= 25:
    print("Dein Zustand ist kritisch!")
else:
    print("Du kannst weiterkämpfen.")

Python prüft die Bedingungen von oben nach unten:

  1. Ist hp kleiner oder gleich 0?
  2. Falls nicht: Ist hp kleiner oder gleich 25?
  3. Falls auch das nicht zutrifft: Führe den else-Block aus.

Sobald eine Bedingung als wahr ausgewertet wird, führt Python den zugehörigen Block aus. Die restlichen Zweige dieser if/elif/else-Kette werden nicht mehr geprüft.

Die Reihenfolge der Bedingungen ist wichtig

Die speziellere Bedingung sollte häufig zuerst stehen:

if hp <= 0:
    print("Du bist besiegt.")
elif hp <= 25:
    print("Dein Zustand ist kritisch!")

Würden wir zuerst hp <= 25 prüfen, träfe diese Bedingung auch bei 0 oder negativen Lebenspunkten zu. Python würde bereits diesen Block ausführen und die Meldung über die Niederlage nicht mehr erreichen.

Bedingungen miteinander verknüpfen

Mit and, or und not kannst Du Bedingungen kombinieren.

and: Beide Bedingungen müssen erfüllt sein

hp = 50
hat_schluessel = True

if hp > 0 and hat_schluessel:
    print("Du kannst die verschlossene Tür öffnen.")

Der gesamte Ausdruck ist nur dann wahr, wenn der Abenteurer noch Lebenspunkte hat und den Schlüssel besitzt.

Du könntest auch hat_schluessel == True schreiben. Bei einer booleschen Variablen ist dieser Vergleich aber unnötig. hat_schluessel selbst liefert bereits True oder False.

or: Mindestens eine Bedingung muss erfüllt sein

gold = 100
hat_schluessel = False

if gold >= 100 or hat_schluessel:
    print("Du kannst die Wache passieren.")

Der Ausdruck ist wahr, weil der Abenteurer genügend Gold besitzt. Dass hat_schluessel den Wert False hat, spielt in diesem Fall keine Rolle.

not: Einen Boolean umkehren

tuer_verschlossen = False

if not tuer_verschlossen:
    print("Die Tür ist bereits offen.")

not kehrt den Boolean um:

  • Aus True wird False.
  • Aus False wird True.

Da tuer_verschlossen hier False ist, ergibt not tuer_verschlossen den Wert True.

while: Wiederholen, solange eine Bedingung erfüllt ist

Eine while-Schleife wiederholt ihren Code-Block, solange ihre Bedingung als wahr ausgewertet wird:

runden = 3

while runden > 0:
    print(f"Runden übrig: {runden}")
    runden = runden - 1

print("Der Kampf ist vorbei.")

Die Ausgabe lautet:

Runden übrig: 3
Runden übrig: 2
Runden übrig: 1
Der Kampf ist vorbei.

Bei jedem Durchlauf wird runden um 1 verringert. Sobald die Variable den Wert 0 erreicht, ist runden > 0 nicht mehr erfüllt und die Schleife endet.

Vorsicht vor Endlosschleifen

Vergisst Du, runden zu verändern, bleibt die Bedingung dauerhaft wahr:

runden = 3

while runden > 0:
    print(f"Runden übrig: {runden}")

Diese Schleife läuft immer weiter, weil runden den Wert 3 behält.

Ein laufendes Python-Programm kannst Du im Terminal normalerweise mit Strg+C abbrechen.

for: Werte nacheinander durchlaufen

Mit einer for-Schleife kannst Du mehrere Werte nacheinander durchlaufen. Für unser erstes Beispiel verwenden wir range(...), das uns eine Reihe von Zahlen liefert:

for runde in range(1, 4):
    print(f"Runde {runde}")

Die Ausgabe lautet:

Runde 1
Runde 2
Runde 3

range(1, 4) beginnt bei 1 und endet vor 4. Der Stop-Wert selbst gehört also nicht mehr dazu.

Du kannst auch eine Schrittweite angeben:

for zahl in range(3, 0, -1):
    print(zahl)

print("Los!")

Die Ausgabe lautet:

3
2
1
Los!

Die drei Werte von range(...) bedeuten:

range(start, stop, schritt)
  • start: erster Wert
  • stop: Grenze, die nicht mehr enthalten ist
  • schritt: Veränderung von einem Wert zum nächsten

Da die Schrittweite -1 beträgt, zählt die Schleife rückwärts.

Eine for-Schleife ist allerdings nicht auf Zahlen beschränkt. Sie kann allgemein die Elemente eines iterierbaren Objekts (Iterable) durchlaufen. Später werden wir sie vor allem für Sammlungen wie Listen und Dictionaries verwenden.

Schleifen mit break und continue steuern

Mit break beendest Du die aktuelle Schleife sofort:

while True:
    befehl = input("> ")

    if befehl == "ende":
        break

    print(f"Du hast {befehl} eingegeben.")

print("Das Spiel wurde beendet.")

Die Bedingung von while True ist immer wahr. Ohne break würde die Schleife deshalb immer weiterlaufen.

Sobald der Spieler ende eingibt, wird break ausgeführt und Python setzt das Programm nach der Schleife fort.

Mit continue überspringst Du den restlichen Code des aktuellen Durchlaufs und beginnst mit dem nächsten:

for zahl in range(1, 6):
    if zahl == 3:
        continue

    print(zahl)

Die Ausgabe lautet:

1
2
4
5

Beim Wert 3 führt Python continue aus. Die print-Anweisung dieses Durchlaufs wird deshalb übersprungen.

Der Dungeon, Schritt 3

Jetzt bauen wir aus den bisherigen Grundlagen einen einfachen Game-Loop.

Öffne dungeon.py und ersetze den bisherigen Inhalt durch folgenden Code:

# Dungeon – Teil 3: Entscheidungen und Game-Loop
print("Du stehst vor dem rostigen Tor eines vergessenen Verlieses.")
print("Ein kalter Luftzug weht Dir entgegen.")

name = input("Wie heißt Du, Abenteurer? ")
gold = int(input("Wie viele Goldmünzen bringst Du mit? "))

hp = 100
max_hp = 100

print()
print(f"Willkommen, {name}!")
print(f"HP: {hp}/{max_hp} | Gold: {gold}")
print()
print("Du stehst in einer dunklen Halle.")
print("Gänge führen nach Norden und Osten.")
print("Verfügbare Befehle: norden, osten, status, ende")

while True:
    befehl = input("\n> ").strip().lower()

    if befehl == "ende":
        print("Du verlässt das Verlies. Bis zum nächsten Mal.")
        break

    elif befehl == "norden":
        print("Du gehst nach Norden und erreichst eine verschlossene Tür.")

    elif befehl == "osten":
        print("Du schleichst nach Osten und überraschst einen Goblin.")

        schaden = 25
        hp = max(hp - schaden, 0)

        print(f"Der Goblin trifft Dich. Du verlierst {schaden} Lebenspunkte.")
        print(f"HP: {hp}/{max_hp}")

        if hp == 0:
            print("Du sinkst zu Boden. Dein Abenteuer endet hier.")
            break

    elif befehl == "status":
        print(f"{name} | HP: {hp}/{max_hp} | Gold: {gold}")

        if hp <= 25:
            print("Dein Zustand ist kritisch!")
        elif hp < max_hp:
            print("Du bist verletzt, kannst aber weiterkämpfen.")
        else:
            print("Du bist unverletzt.")

    else:
        print("Das verstehe ich nicht.")
        print("Verfügbare Befehle: norden, osten, 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!
HP: 100/100 | Gold: 50

Du stehst in einer dunklen Halle.
Gänge führen nach Norden und Osten.
Verfügbare Befehle: norden, osten, status, ende

> osten
Du schleichst nach Osten und überraschst einen Goblin.
Der Goblin trifft Dich. Du verlierst 25 Lebenspunkte.
HP: 75/100

> status
Karl | HP: 75/100 | Gold: 50
Du bist verletzt, kannst aber weiterkämpfen.

> norden
Du gehst nach Norden und erreichst eine verschlossene Tür.

> ende
Du verlässt das Verlies. Bis zum nächsten Mal.

Was im Game-Loop passiert

Der Kern unseres Spiels ist diese Schleife:

while True:
    befehl = input("\n> ").strip().lower()

while True startet eine Schleife, deren Bedingung immer wahr ist. Bei jedem Durchlauf wartet input(...) auf einen neuen Befehl. Beendet wird die Schleife erst durch eines unserer break.

Die beiden String-Methoden bereiten die Eingabe für den Vergleich auf:

.strip()
.lower()

strip() entfernt Leerzeichen und andere Whitespace-Zeichen am Anfang und Ende. lower() wandelt Großbuchstaben in Kleinbuchstaben um.

Dadurch werden beispielsweise diese Eingaben gleich behandelt:

osten
OSTEN
 Osten

Anschließend prüft die if/elif/else-Kette, welchen Befehl der Spieler eingegeben hat.

Beim Befehl osten verändert sich der Player State:

hp = max(hp - schaden, 0)

Zuerst wird der Schaden von den Lebenspunkten abgezogen. max(...) gibt anschließend den größeren der beiden Werte zurück. Dadurch kann hp hier nicht unter 0 fallen.

Hat der Abenteurer keine Lebenspunkte mehr, beendet break den Game-Loop:

if hp == 0:
    print("Du sinkst zu Boden. Dein Abenteuer endet hier.")
    break

Der Befehl status enthält ein verschachteltes if: Innerhalb des bereits ausgewählten elif-Blocks wird eine weitere Bedingung geprüft.

elif befehl == "status":
    print(f"{name} | HP: {hp}/{max_hp} | Gold: {gold}")

    if hp <= 25:
        print("Dein Zustand ist kritisch!")

So kann der Dungeon zuerst auf den Befehl reagieren und anschließend abhängig von den aktuellen Lebenspunkten eine passende Meldung ausgeben.

Stolperfallen

  • = statt == verwenden: Ein einzelnes = weist einen Wert zu. Für einen Vergleich benötigst Du ==.

  • Den Doppelpunkt vergessen: Nach if, elif, else, while und for muss ein Doppelpunkt stehen.

  • Code falsch einrücken: Die Einrückung bestimmt, welche Anweisungen zu einem Block gehören. Verwende üblicherweise vier Leerzeichen und mische Tabs und Leerzeichen nicht.

  • Bedingungen in der falschen Reihenfolge prüfen: Eine allgemeinere Bedingung kann eine speziellere abfangen. Prüfe beispielsweise hp <= 0 vor hp <= 25.

  • Eine Endlosschleife bauen: Eine while-Schleife endet nur, wenn ihre Bedingung irgendwann als falsch ausgewertet oder die Schleife anderweitig beendet wird. Bei while True brauchst Du dafür in unserem Beispiel break.

  • break außerhalb einer Schleife verwenden: break funktioniert nur innerhalb einer while- oder for-Schleife.

  • Groß- und Kleinschreibung beim Input übersehen: Ohne .lower() wären "osten" und "OSTEN" zwei unterschiedliche Strings.

Übungen

1. Einen Heiltrank verwenden

Erweitere den Game-Loop um den Befehl heilen. Er soll dem Abenteurer 20 Lebenspunkte zurückgeben, aber niemals mehr als max_hp.

Lösung Füge vor dem abschließenden `else` einen weiteren `elif`-Block ein:
elif befehl == "heilen":
    heilung = 20
    hp = min(hp + heilung, max_hp)

    print("Du trinkst einen Heiltrank.")
    print(f"HP: {hp}/{max_hp}")
`min(...)` gibt den kleineren der beiden Werte zurück. Dadurch kann `hp` nicht über `max_hp` steigen. Vergiss außerdem nicht, `heilen` zur Liste der verfügbaren Befehle hinzuzufügen.

2. Einen Countdown programmieren

Schreibe eine for-Schleife, die von 3 bis 1 herunterzählt und anschließend "Los!" ausgibt.

Lösung
for zahl in range(3, 0, -1):
    print(zahl)

print("Los!")
`range(3, 0, -1)` beginnt bei `3` und zählt in Schritten von `-1` rückwärts. Die Grenze `0` ist nicht mehr enthalten.

Vertiefung

Für das Verzweigen anhand von Werten bietet Python seit Version 3.10 mit match und case eine weitere Möglichkeit, die je nach Problem übersichtlicher sein kann als eine längere if/elif-Kette:

match/case: Strukturelles Pattern Matching

Im nächsten Teil

Unsere Räume bestehen momentan noch aus einzelnen if- und elif-Blöcken. Mit jedem neuen Raum und Befehl würde der Game-Loop dadurch länger und unübersichtlicher.

In Teil 4 lernst Du deshalb Sammlungen kennen: Listen und Dictionaries. Mit ihnen beschreiben wir Räume, Ausgänge und ein richtiges Inventar als Daten, statt jeden möglichen Weg fest in den Game-Loop zu schreiben.

0 Kommentare

Noch keine Kommentare. Sei der/die Erste!