Was sind Zustandsautomaten und wozu werden sie benötigt?

Computerprogramme führen Code aus. Oft ist es abhängig von Bedingungen, welcher Code ausgeführt wird. Bei komplizierteren Programmen kann die Prüfung von Bedingungen unübersichtlich werden:

Bei PGZ Blaster Version 02 könnten wir zum Beispiel eine Variable spiel_laeuft haben, die True oder False sein kann. Bei einem Tastendruck zum Beispiel müssen wir dann erst mal schauen: Läuft das Spiel? Hat der Spieler noch ein Schiff? Oder wurde der Spieler grad getroffen? Wie lang ist der Treffer her? etc...

Sehr viel einfacher und übersichtlicher wird es, wenn wir folgende vier Zustände definieren...

  • BEREIT – Vor Beginn des Spiels
  • SPIEL – Während des Spiels
  • GETROFFEN – Nach einem Treffer, wenn noch ein weiteres Schiff vorhanden ist.
  • SPIEL_VORBEI – Wenn das Spiel zuende ist

.... und dann festlegen, welche Ereignisse den Zustand ändern können:

  • Im Zustand BEREIT kommen wir durch einen beliebigen Tastendruck in den Zustand SPIEL.
  • Im Zustand SPIEL kommen wir durch einen Treffer des Schiffs in den Zustand GETROFFEN, wenn noch ein Schiff vorhanden ist, sonst in den Zustand SPIEL_VORBEI.
  • Im Zustand SPIEL_VORBEI geht es (wiederum nach 4 Sekunden Pause) in den Zustand BEREIT.

Zustandsautomaten lassen sich sehr gut zeichen. Im Fall von PGZ Blaster 02 sieht das dann so aus:

Beispiele für Zustandsabfragen und -übergänge aus dem Code des PGZ Blaster 02

Bei Tastendruck im Zustand BEREIT schaltet das Spiel in den Zustand SPIEL.
Beim Drücken der Leertaste im Zustand SPIEL feuert das Schiff eine Rakete ab.

def on_key_down():
    if spiel.zustand == Zustand.BEREIT:
        spiel.zustand = Zustand.SPIEL
        return

    if keyboard.space and spiel.zustand == Zustand.SPIEL:
        spiel.schiff.rakete_abfeuern()

Nur im Zustand SPIEL werden die Spielobjekte (in spiel.aktualisiere()) aktualisiert:

def update():
    if spiel.zustand == Zustand.SPIEL:
        spiel.aktualisiere()

Hier die komplette Funktion draw(). Hier sind die Kommentare im Code:

def draw():
    screen.fill((255, 255, 255))

    # Textanzeige im Zustand BEREIT:
    if spiel.zustand == Zustand.BEREIT:
        textnachricht("PRESS ANY KEY TO START")

    # Zeichnen der Spielobjekte im Zustand SPIEL:
    if spiel.zustand == Zustand.SPIEL:
        for actor in spiel.raketen + spiel.bomben + spiel.ufos:
            actor.draw()
        spiel.schiff.draw()

    # Textanzeige im Zustand GETROFFEN:
    if spiel.zustand == Zustand.GETROFFEN:
        textnachricht("YOU'VE BEEN HIT")

    # Textanzeige im Zustand SPIEL_VORBEI:
    if spiel.zustand == Zustand.SPIEL_VORBEI:
        textnachricht("GAME OVER")

    # Punktestand und Anzahl Schiffe nur anzeigen, wenn der Zunstand nicht BEREIT ist:
    if spiel.zustand != Zustand.BEREIT:
        screen.draw.text("Ships: " + str(spiel.schiff.leben),
                         (20, H-30),
                         color="black")

        screen.draw.text("Score: " + str(spiel.schiff.punkte),
                         (W-120, H-30),
                         color="black")
Ein einfacheres Beispiel für einen Zustandsautomaten
from enum import Enum

class Zustand(Enum):
    HUNGRIG = 0
    SATT = 1
    
zustand = Zustand.HUNGRIG
    
def essen_anbieten():
    global zustand
    
    if zustand == Zustand.HUNGRIG:
        print("Danke, ich esse gerne eine Portion!")
        zustand = Zustand.SATT
        return
        
    if zustand == Zustand.SATT:
        print("Nein danke, ich habe keinen Hunger!")
        return
    
def sport():
    global zustand
    
    if zustand == Zustand.HUNGRIG:
        print("Mit leerem Magen mache ich lieber keinen Sport!")
        return
    
    if zustand == Zustand.SATT:
        print("Ich trainiere jetzt eine Runde!")
        zustand = Zustand.HUNGRIG
        return
    
Zuletzt geändert: Donnerstag, 13. März 2025, 05:42