@reb55: Einen gravierenden Fehler hat Sirius3 doch genannt: `links_drehen()` und `rechts_drehen()` machen in Deiner `labyrinth_lösen()`-Funktion nichts. Die Aufrufe kannst Du einfach löschen ohne das sich etwas am Programmablauf ändert. Sehr wahrscheinlich sollte da aber was passieren. Mit dem Rückgabewert der Funktionen, damit die einen Sinn und Einfluss auf den Programmablauf haben.
Zu den Funktionen wie sie im Original stehen ist noch zu sagen, dass man nach dem Doppelpunkt einen Zeilenumbruch setzen sollte. Auch Blöcke die nur aus einer Zeile bestehen werden wegen der besseren Lesbarkeit in eine eigene Zeile gesetzt.
Noch ungünstiger ist das bei den beiden ``continue`` die so ”versteckt” werden. ``continue`` an sich würde ich ja schon meiden wo es geht, weil das ein unbedingter Sprung ist, der beim Erweitern von Code oder dem herausziehen von Code aus einer Schleife in eine eigene Funktion Probleme bereiten kann.
Weitere Anmwerkungen: Eingerückt wird per Konvention vier Leerzeichen pro Ebene.
Namen sollten keine kryptischen Abkürzungen enthalten, oder gar nur daraus bestehen. `pygame` braucht man nicht abkürzen und `random` schon gar nicht zu `rnd` verstümmeln. Das wird ja nur einmal verwendet und `random.shuffle(…)` ist so schön lesbar und der Quelltext macht da ohne Not ein `rnd.shuffle()` draus. Was soll `ZE_BH` bedeuten? Bei `nachb` hat's echt nicht mehr gereicht um `arn` dran zu hängen?
Bei den Werten fallen unter die kryptischen Abkürzungen auch "l", "r", "o", und "u". Die übrigens irgendwie redundant sind, denn die Himmelsrichtungen, die ja auch verwendet werden, erfüllen den gleichen Zweck. Und man würde da keine Zeichenketten für verwenden, oder zumindest Konstanten dafür definieren, damit man bei einem Vertipper wenigstens einen `NameError` bekommt und das Programm nicht einfach weiterläuft, aber nicht das macht was man erwartet. Das `enum`-Modul bietet mit `Flag` eine passende Klasse um Konstanten und Kombinationen mit Testmöglichkeiten für die Richtungen zu erstellen. Wenn man da nur einen Satz an Konstanten verwendet, wird auch das Lösen einfacher, weil man da nicht Himmelsrichtungen auf oben, unten, und so weiter abgleichen muss, sondern immer nur *eine* Richtung hat.
Umlaute in Namen kann Python zwar, aber nicht alle Werkzeuge mit denen man Quelltext allgemein oder Python-Quelltext im besonderen verarbeiten kann. Ich würde da ASCII-Only bleiben bei den Namen.
Konstantennamen werden per Konvention KOMPLETT_GROSS geschrieben.
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Wörterbücher werden häufig so benannt, dass man am Namen erkennt das es eine Abbildung ist und was auf was abgebildet wird. Also beispielsweise `RICHTUNG_AUF_GEGENRICHTUNG` anstatt `RICHTUNG_INVERS`. Also nach dem Muster `schluessel_auf_wert`, wobei `schluessel` und `wert` in dem Namen die Bedeutung von einem Schlüssel und `wert` die Bedeutung von einem Wert vermitteln.
``{b for b in "lrou"}`` würde man besser als ``set("lrou")`` schreiben.
Beim erstellen von `raster` ist das irgendwie unsinnig erst ein `i` aus dem Bereich ZEILEN * SPALTEN zu bilden um das dann durch Teilen und Modulo wieder in eine Zeilen/Spalten-Koordinate aufzuteilen. Und wenn man die nicht noch mal extra an einen Namen bindet, liesse sich das auch kompakter als „dict comprehension“ ausdrücken.
`besucht` wird im gleichen Namensraum mal an eine Menge und danach an eine Liste gebunden. Ursprünglich ist dieser Namensraum dann auch noch das Modul und Funktionen greifen einfach so darauf zu ohne das als Argument übergeben zu bekommen. Argh. `labyrinth_erstellen()` sollte das als Argument übergeben bekommen (beziehungsweise bei Bedarf selbst erstellen) und `labyrinth_loesen()` sollte das selbst erstellen und die besuchten Positionen als Ergebnis liefern. Statt da eine leere Liste zu erwarten und die zu füllen. Das geht den Aufrufer nix an.
`labyrinth_erstellen()` braucht das `raster`, das muss man als Argument übergeben und von dort an `nachbarn_ermitteln()` weiterreichen. Ebenso braucht man das `raster` zum `labyrinth_loesen()`.
Die ganzen Positionen sollte man nicht durch einfache Tupel repräsentieren sondern `pygame.Vector2`-Objekte verwenden. Die haben Operationen wie addieren von Vektoren. So etwas muss man nicht selbst noch mal nachbauen.
Deine `links_drehen()` und `rechts_drehen()` geben `None` zurück wenn man eine unbekannte Richtung übergibt (was zum Beispiel passieren kann wenn man sich bei einem f-Zeichenkettenliteral mit einer Richtung mal vertippt hat). Wenn es ``if``-Fälle gibt die nicht alles abdecken und oft auch bei ``if``/``elif`` ohne ein ``else`` am Ende, sollte man mindestens ein ``assert`` einbauen für den Fall der eigentlich nie eintreten kann. Wenn man Fehler macht, sind das oft die interessanten Stellen, von denen man dann wissen möchte.
`pg_quit()` ist unfertig. Das gibt `True` zurück wenn der Benutzer abbrechen will. Aber wenn nicht, dann wird implizit `None` zurück gegeben. Da weiss man als Leser dann nicht ob da noch Code fehlt, oder ob das Absicht ist. Und es sollte auch nicht ein Wert zurückgegeben werden der zufällig auch ”falsy” ist, sondern `False` als Gegenstück zu `True`.
Die erste `while not pg_quit():`-Schleife ist komisch. Da passiert solange nichts bis der Benutzer das Programm abbrechen will, was daraufhin aber gar nicht abbricht sondern mit der nächsten Schleife weiter macht. Da würde ich mich als Benutzer verarscht vorkommen wenn man durch klicken auf das X in der Fensterleiste im Programm einen Schritt weiter kommt. Sofern da überhaupt jemand drauf kommt, denn intuitiv ist das ja nicht gerade.
Die ``while``-Schleife danach ist nicht so viel besser. Was ist denn das für ein Unsinn der da mit `i` angestellt wird? Und warum wird am Ende der letzte Pfadschritt immer und immer wieder gezeichnet? In der gleichen Farbe, also so dass man das auch gar nicht wahrnehmen kann‽
Zwischenstand (ungetestet):
Code: Alles auswählen
#!/usr/bin/env python3
import random
import pygame
#
# TODO Richtungen werden mal durch "l", "r", "o", "u" repräsentiert und mal
# durch Zeichenketten die Himmelsrichtungen beschreiben. Beides durch *einen*
# Satz an Konstanten ersetzen die auf `enum.Flag` aufbauen (und nicht kryptisch
# abgekürzt sind).
#
# TODO Positionen sauber in Weltkoordinaten und Bildschirmkoordinaten trennen
# und nicht Bildschirmkoordinaten für alles verwenden.
#
BREITE = HOEHE = 1000
SPALTEN = ZEILEN = 30
ZELLENKANTENLAENGE = BREITE // SPALTEN
RICHTUNG_AUF_DELTA_RANDKOORDINATEN = {
"l": [(0, 0), (0, ZELLENKANTENLAENGE)],
"r": [(ZELLENKANTENLAENGE, 0), (ZELLENKANTENLAENGE, ZELLENKANTENLAENGE)],
"o": [(0, 0), (ZELLENKANTENLAENGE, 0)],
"u": [(0, ZELLENKANTENLAENGE), (ZELLENKANTENLAENGE, ZELLENKANTENLAENGE)],
}
RICHTUNG_AUF_DELTA_KOORDINATEN = {
"l": (-ZELLENKANTENLAENGE, 0),
"r": (ZELLENKANTENLAENGE, 0),
"o": (0, -ZELLENKANTENLAENGE),
"u": (0, ZELLENKANTENLAENGE),
}
RICHTUNG_AUF_GEGENRICHTUNG = {"l": "r", "r": "l", "o": "u", "u": "o"}
HINTERGRUNDFARBE = pygame.Color("Black")
LINIENFARBE = pygame.Color("White")
SUCHPFADFARBE = pygame.Color("darkorchid4")
#
# TODO Positionen durch `pygame.Vector2` repräsentieren, dann kann man sich
# diese Funktion sparen, denn 2D-Vektoren kann man addieren.
#
def add_position(position_a, position_b):
return position_a[0] + position_b[0], position_a[1] + position_b[1]
def zeichne_zelle(screen, position, waende):
for wand in waende:
delta_von, delta_bis = RICHTUNG_AUF_DELTA_RANDKOORDINATEN[wand]
pygame.draw.line(
screen,
LINIENFARBE,
add_position(position, delta_von),
add_position(position, delta_bis),
2,
)
def should_quit():
for ereignis in pygame.event.get():
if ereignis.type == pygame.QUIT or (
ereignis.type == pygame.KEYDOWN and ereignis.key == pygame.K_ESCAPE
):
return True
return False
def nachbarn_ermitteln(raster, position):
nachbarn = []
for richtung, delta in RICHTUNG_AUF_DELTA_KOORDINATEN.items():
neue_position = add_position(position, delta)
if neue_position in raster:
nachbarn.append((richtung, neue_position))
random.shuffle(nachbarn)
return nachbarn
def labyrinth_erstellen(raster, aktuelle_position, richtung_von, besucht=None):
if besucht is None:
besucht = set()
besucht.add(aktuelle_position)
raster[aktuelle_position].remove(richtung_von)
for richtung_nach, neue_position in nachbarn_ermitteln(
raster, aktuelle_position
):
if neue_position not in besucht:
raster[aktuelle_position].remove(richtung_nach)
labyrinth_erstellen(
raster,
neue_position,
RICHTUNG_AUF_GEGENRICHTUNG[richtung_nach],
besucht,
)
def rechts_drehen(richtung):
if richtung == "Ost":
return "Sued"
if richtung == "Sued":
return "West"
if richtung == "West":
return "Nord"
if richtung == "Nord":
return "Ost"
assert False, f"unbekannte Richtung {richtung!r}"
def links_drehen(richtung):
if richtung == "Ost":
return "Nord"
if richtung == "Sued":
return "Ost"
if richtung == "West":
return "Sued"
if richtung == "Nord":
return "West"
assert False, f"unbekannte Richtung {richtung!r}"
def labyrinth_loesen(raster, position, richtung):
pfadpositionen = [position]
for _ in range(0, 9):
if richtung == "Ost":
vorne = "r"
rechts = "u"
links = "o"
rueck = "l"
if richtung == "Sued":
vorne = "u"
rechts = "l"
links = "r"
rueck = "o"
if richtung == "West":
vorne = "l"
rechts = "o"
links = "u"
rueck = "r"
if richtung == "Nord":
vorne = "o"
rechts = "r"
links = "l"
rueck = "u"
if vorne not in raster[position]:
position = add_position(
position, RICHTUNG_AUF_DELTA_KOORDINATEN[vorne]
)
elif vorne in raster[position] and rechts not in raster[position]:
position = add_position(
position, RICHTUNG_AUF_DELTA_KOORDINATEN[rechts]
)
rechts_drehen(richtung)
elif vorne in raster[position] and links not in raster[position]:
position = add_position(
position, RICHTUNG_AUF_DELTA_KOORDINATEN[links]
)
links_drehen(richtung)
else:
rechts_drehen(richtung)
rechts_drehen(richtung)
position = add_position(
position, RICHTUNG_AUF_DELTA_KOORDINATEN[rueck]
)
pfadpositionen.append(position)
return pfadpositionen
def main():
pygame.init()
screen = pygame.display.set_mode([BREITE, HOEHE])
raster = {
(
spalten_index * ZELLENKANTENLAENGE,
zeilen_index * ZELLENKANTENLAENGE,
): set("lrou")
for spalten_index in range(SPALTEN)
for zeilen_index in range(ZEILEN)
}
labyrinth_erstellen(raster, (0, 0), "l")
pfadpositionen = labyrinth_loesen(raster, (0, 0), "Ost")
screen.fill(HINTERGRUNDFARBE)
for position, waende in raster.items():
zeichne_zelle(screen, position, waende)
pygame.display.flip()
for position in pfadpositionen:
pygame.draw.rect(
screen,
SUCHPFADFARBE,
(position, (ZELLENKANTENLAENGE, ZELLENKANTENLAENGE)),
)
for position, waende in raster.items():
zeichne_zelle(screen, position, waende)
pygame.display.flip()
while not should_quit():
pygame.display.flip() # TODO Busy waiting ausbremsen.
pygame.quit()
if __name__ == "__main__":
main()