Skip to content

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

Learning Python (Part 3): Decisions and loops

Comparisons, conditions, and loops, plus a game loop that lets the dungeon respond to commands and change its state during play.

Long-exposure shot from above: Red taillight trails circle a nighttime roundabout with four exits many times, forming a glowing ring.

Up to and including part 2, our program ran stubbornly from top to bottom. It could store and change values, but every time it started, it ran the same statements in the same order.

Today the dungeon learns to make decisions and to repeat statements. For that, we use conditions and loops. By the end, our text adventure has a game loop that waits for commands and responds differently to the player's input.

Comparisons return Booleans

Before Python can make a decision, it needs a condition. To start, we'll mainly use comparisons for that. Their result is either True or False.

You already know these Booleans from part 2 as the bool data type:

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

There are several operators for comparisons:

Operator Meaning Example
== equal to hp == 100
!= not equal to hp != 0
> greater than gold > 50
< less than hp < 25
>= greater than or equal to level >= 5
<= less than or equal to hp <= 25

You can assign the result of a comparison to a variable like any other value:

hp = 20
ist_verletzt = hp < 100

print(ist_verletzt)

The output is:

True

ist_verletzt is German for “is injured.”

= and == aren't the same

A single equals sign assigns a value to a name:

hp = 100

A double equals sign, on the other hand, compares two values:

hp == 100

So the two expressions do completely different jobs:

  • hp = 100 assigns the value 100 to hp.
  • hp == 100 checks whether hp has the value 100.

Mixing up = and == happens easily when you're starting out. Depending on where it happens, it leads to a syntax error or just to different code than you intended.

Conditions with if

With if, you run a block of code only if a condition evaluates to true:

hp = 20

if hp <= 25:
    print("Careful, you're badly injured!")

Since 20 <= 25 gives True, the indented line runs.

If the adventurer has 80 hit points instead, the comparison is False. Python skips the indented block:

hp = 80

if hp <= 25:
    print("Careful, you're badly injured!")

print(f"You have {hp} hit points.")

The output is:

You have 80 hit points.

Indentation forms code blocks

The if condition is followed by a colon. The statements that belong to the if are indented:

if hp <= 25:
    print("Careful!")
    print("You should heal yourself.")

print("This line always runs.")

The two indented print statements together form a code block. They run only if the condition is met.

The last line isn't indented, so it no longer belongs to the if block. It runs regardless of the condition.

Python code is usually indented with four spaces. Most code editors do this automatically as soon as you press Enter after the colon.

Multiple cases with elif and else

With elif, you can check further conditions. The abbreviation stands for else if. A final else kicks in if none of the previous conditions was met:

hp = 20

if hp <= 0:
    print("You have been defeated.")
elif hp <= 25:
    print("Your condition is critical!")
else:
    print("You can keep fighting.")

Python checks the conditions from top to bottom:

  1. Is hp less than or equal to 0?
  2. If not: Is hp less than or equal to 25?
  3. If that isn't true either: Run the else block.

As soon as a condition evaluates to true, Python runs the matching block. The remaining branches of this if/elif/else chain are skipped.

The order of the conditions matters

The more specific condition should often come first:

if hp <= 0:
    print("You have been defeated.")
elif hp <= 25:
    print("Your condition is critical!")

If we checked hp <= 25 first, that condition would also be true at 0 or negative hit points. Python would run that block and never get to the defeat message.

Combining conditions

With and, or, and not, you can combine conditions.

and: Both conditions must be met

hp = 50
hat_schluessel = True

if hp > 0 and hat_schluessel:
    print("You can open the locked door.")

The whole expression is true only if the adventurer still has hit points and has the key (hat_schluessel is German for “has key”).

You could also write hat_schluessel == True. With a Boolean variable, though, this comparison is unnecessary. hat_schluessel itself already gives True or False.

or: At least one condition must be met

gold = 100
hat_schluessel = False

if gold >= 100 or hat_schluessel:
    print("You can get past the guard.")

The expression is true because the adventurer has enough gold. That hat_schluessel has the value False doesn't matter in this case.

not: Flipping a Boolean

tuer_verschlossen = False

if not tuer_verschlossen:
    print("The door is already open.")

not flips the Boolean:

  • True becomes False.
  • False becomes True.

Since tuer_verschlossen (door locked) is False here, not tuer_verschlossen gives True.

while: Repeating as long as a condition is met

A while loop repeats its code block as long as its condition evaluates to true:

runden = 3

while runden > 0:
    print(f"Rounds left: {runden}")
    runden = runden - 1

print("The fight is over.")

The output is:

Rounds left: 3
Rounds left: 2
Rounds left: 1
The fight is over.

On each pass, runden (rounds) goes down by 1. As soon as the variable reaches 0, runden > 0 is no longer true, and the loop ends.

Watch out for infinite loops

If you forget to change runden, the condition stays true forever:

runden = 3

while runden > 0:
    print(f"Rounds left: {runden}")

This loop runs on and on because runden keeps the value 3.

You can usually stop a running Python program in the terminal with Ctrl+C.

for: Going through values one by one

With a for loop, you can go through several values one after another. For our first example, we use range(...), which gives us a sequence of numbers:

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

The output is:

Round 1
Round 2
Round 3

range(1, 4) starts at 1 and ends before 4. So the stop value itself isn't included.

You can also specify a step:

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

print("Go!")

The output is:

3
2
1
Go!

The three values of range(...) mean:

range(start, stop, schritt)
  • start: the first value
  • stop: the limit, which isn't included
  • schritt: the change from one value to the next (German for “step”)

Since the step is -1, the loop counts backward.

A for loop isn't limited to numbers, though. In general, it can go through the elements of any iterable. Later on, we'll mainly use it for collections such as lists and dictionaries.

Controlling loops with break and continue

With break, you end the current loop immediately:

while True:
    befehl = input("> ")

    if befehl == "quit":
        break

    print(f"You entered {befehl}.")

print("The game has ended.")

The condition of while True is always true. Without break, the loop would keep running forever.

As soon as the player types quit, break runs, and Python continues the program after the loop. befehl is German for command.

With continue, you skip the rest of the code in the current pass and start the next one:

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

    print(zahl)

The output is:

1
2
4
5

At the value 3, Python runs continue. So the print statement in that pass is skipped.

The dungeon, step 3

Now we'll use these basics to build a simple game loop.

Open dungeon.py and replace the previous content with the following code:

# Dungeon—Part 3: Decisions and game loop
print("You stand before the rusty gate of a forgotten dungeon.")
print("A cold draft blows toward you.")

name = input("What's your name, adventurer? ")
gold = int(input("How many gold coins are you bringing? "))

hp = 100
max_hp = 100

print()
print(f"Welcome, {name}!")
print(f"HP: {hp}/{max_hp} | Gold: {gold}")
print()
print("You're standing in a dark hall.")
print("Passages lead north and east.")
print("Available commands: north, east, status, quit")

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

    if befehl == "quit":
        print("You leave the dungeon. Until next time.")
        break

    elif befehl == "north":
        print("You head north and reach a locked door.")

    elif befehl == "east":
        print("You sneak east and surprise a goblin.")

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

        print(f"The goblin hits you. You lose {schaden} hit points.")
        print(f"HP: {hp}/{max_hp}")

        if hp == 0:
            print("You sink to the ground. Your adventure ends here.")
            break

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

        if hp <= 25:
            print("Your condition is critical!")
        elif hp < max_hp:
            print("You're injured, but you can keep fighting.")
        else:
            print("You're unharmed.")

    else:
        print("I don't understand that.")
        print("Available commands: north, east, status, quit")

Run the program:

python dungeon.py

One possible run looks like this:

You stand before the rusty gate of a forgotten dungeon.
A cold draft blows toward you.
What's your name, adventurer? Karl
How many gold coins are you bringing? 50

Welcome, Karl!
HP: 100/100 | Gold: 50

You're standing in a dark hall.
Passages lead north and east.
Available commands: north, east, status, quit

> east
You sneak east and surprise a goblin.
The goblin hits you. You lose 25 hit points.
HP: 75/100

> status
Karl | HP: 75/100 | Gold: 50
You're injured, but you can keep fighting.

> north
You head north and reach a locked door.

> quit
You leave the dungeon. Until next time.

What happens in the game loop

The heart of our game is this loop:

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

while True starts a loop whose condition is always true. On each pass, input(...) waits for a new command. The loop ends only when one of our break statements runs.

The two string methods prepare the input for the comparison:

.strip()
.lower()

strip() removes spaces and other whitespace characters at the start and end. lower() converts uppercase letters to lowercase.

That way, inputs like these are treated the same:

east
EAST
 East

Then the if/elif/else chain checks which command the player entered.

The east command changes the player state:

hp = max(hp - schaden, 0)

First, the damage is subtracted from the hit points. max(...) then returns the larger of the two values. That way, hp can't drop below 0 here.

If the adventurer has no hit points left, break ends the game loop:

if hp == 0:
    print("You sink to the ground. Your adventure ends here.")
    break

The status command contains a nested if: Inside the elif block that was already selected, another condition is checked.

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

    if hp <= 25:
        print("Your condition is critical!")

This way, the dungeon can first respond to the command and then print a fitting message depending on the current hit points.

Pitfalls

  • Using = instead of ==: A single = assigns a value. For a comparison, you need ==.

  • Forgetting the colon: if, elif, else, while, and for must be followed by a colon.

  • Indenting code incorrectly: The indentation determines which statements belong to a block. Stick to four spaces, and don't mix tabs and spaces.

  • Checking conditions in the wrong order: A more general condition can catch a more specific one. For example, check hp <= 0 before hp <= 25.

  • Building an infinite loop: A while loop ends only if its condition eventually evaluates to false or the loop is ended some other way. With while True, our example needs break for that.

  • Using break outside a loop: break only works inside a while or for loop.

  • Forgetting that input can differ in letter case: Without .lower(), "east" and "EAST" would be two different strings.

Exercises

1. Use a healing potion

Add a heal command to the game loop. It should give the adventurer back 20 hit points, but never more than max_hp.

Solution Add another `elif` block before the final `else`:
elif befehl == "heal":
    heilung = 20
    hp = min(hp + heilung, max_hp)

    print("You drink a healing potion.")
    print(f"HP: {hp}/{max_hp}")
`min(...)` returns the smaller of the two values. That way, `hp` can't rise above `max_hp`. Don't forget to add `heal` to the list of available commands, too.

2. Program a countdown

Write a for loop that counts down from 3 to 1 and then prints "Go!".

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

print("Go!")
`range(3, 0, -1)` starts at `3` and counts backward in steps of `-1`. The limit `0` isn't included.

Going deeper

For branching on values, Python 3.10 and later also offers match and case. Depending on the problem, that can be easier to read than a long if/elif chain:

match/case: Structural pattern matching

Next up

Right now, our rooms still consist of individual if and elif blocks. With every new room and command, the game loop would get longer and harder to follow.

That's why, in part 4, you learn about collections: lists and dictionaries. With them, we describe rooms, exits, and a real inventory as data, instead of hard-coding every possible path into the game loop.

0 comments

No comments yet. Be the first!