Logikfehler?!

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
reb55
User
Beiträge: 8
Registriert: Donnerstag 25. August 2022, 13:09

Hallo, ich habe folgendes Problem:
Ich wollte den rechte-Hand-Algorithmus in ein Labyrinth programmieren; das Labyrinth kommt aus einem Labyrinthgenerator, das der Youtuber Gravitar entworfen hat. Es ist auf Github veröffentlicht.
Meine Zeilen sind die folgenden:
def rechts_drehen(richtung):
if richtung == "Ost": return "Sued"
if richtung == "Sued": return "West"
if richtung == "West": return "Nord"
if richtung == "Nord": return "Ost"
def links_drehen(richtung):
if richtung == "Ost": return "Nord"
if richtung == "Sued": return "Ost"
if richtung == "West": return "Sued"
if richtung == "Nord": return "West"

def labyrinth_lösen(pos,richtung):
#ziel = ((SPALTEN)*ZE_BH, (ZEILEN)*ZE_BH)
besucht. append(pos)

for n 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[pos]: # vorne frei
pos = add_pos(pos,delta_nachbarn[vorne])

elif (vorne in raster[pos]) and (rechts not in raster[pos]): # rechts drehen
pos = add_pos(pos,delta_nachbarn[rechts])
rechts_drehen(richtung)

elif (vorne in raster[pos]) and (links not in raster[pos]): #links drehen

pos = add_pos(pos,delta_nachbarn[links])
links_drehen(richtung)

else: #umdrehen
rechts_drehen(richtung)
rechts_drehen(richtung)
pos = add_pos(pos,delta_nachbarn[rueck])

besucht.append(pos)

Normalerweise käme hier eine while Schleife zum Einsatz; zur Fehlersuche habe ich aber vorerst eine for-Schleife benutzt.
Mein Problem ist, dass die Schleife nach spätestens 8 Durchgängen auf die alte Position zurückspringt und dann bis Ende der Schleife immer vor- und zurückspringt. Am Anfang funktioniert sie problemlos.
Ich brauche jetzt einen kurzen Anstoß wo das Problem liegt; ich habe mich festgefressen und sehe den Logikfehler (sofern einer vorliegt) nicht.
Es ist mir klar dass die Programmierung umständlich ist; ich möchte aber zuerst wissen was da schiefläuft bevor ich die Geschichte optimieren.
Danke für Eure Hilfe.
Sirius3
User
Beiträge: 18266
Registriert: Sonntag 21. Oktober 2012, 17:20

Solche langen if-Kaskaden wie in `rechts_drehen` oder `links_drehen` löst man in Python mit einem Wörterbuch:

Code: Alles auswählen

RECHTS_DREHUNG = {
    "Ost": "Sued",
    "Sued": "West",
    "West": "Nord",
    "Nord": "Ost",
}
rechts_drehen = RECHTS_DREHUNG.get
Oder, da wir hier eine relativ einfache Relation haben, mit etwas Rechenkraft:

Code: Alles auswählen

RICHTUNGEN = ["Ost", "Sued", "West", "Nord"]

def rechts_drehen(richtung):
    return RICHTUNGEN[(RICHTUNGEN.index(richtung) + 1) % len(RICHTUNGEN)]
    
def links_drehen(richtung):
    return RICHTUNGEN[(RICHTUNGEN.index(richtung) - 1) % len(RICHTUNGEN)]
Die Wörterbuchmethode verwendet man auch für die Auflösung von vorne, rechts, links und rueck.
elif benutzt man ja deshalb, weil man sich auschließende Bedingungen hat. Wenn also im ersten if geprüft wird, dass vorne nicht in raster[pos] ist, dann ist automatishc in allen elif's vorne in raster[pos], diese Bedingung muß mal also nicht nochmal prüfen.

Code: Alles auswählen

RELATIVE_POSITIONEN = {
    "Ost": ('r', 'u', 'o', 'l'),
    "Sued": ('u', 'l', 'r', 'o'),
    "West": ('l', 'o', 'u', 'r'),
    "Nord": ('o', 'r', 'l', 'u'),
}

def labyrinth_lösen(pos, richtung):
    #ziel = ((SPALTEN)*ZE_BH, (ZEILEN)*ZE_BH)
    besucht = [pos]
    for n in range(9):
        vorne, rechts, links, rueck = RELATIVE_POSITIONEN[richtung]
        
        if vorne not in raster[pos]:
            # vorne frei
            pos = add_pos(pos, delta_nachbarn[vorne])
        elif rechts not in raster[pos]:
            # rechts drehen
            pos = add_pos(pos, delta_nachbarn[rechts])
            rechts_drehen(richtung)
        elif links not in raster[pos]:
            # links drehen
            pos = add_pos(pos, delta_nachbarn[links])
            links_drehen(richtung)  
        else:
            # umdrehen
            pos = add_pos(pos, delta_nachbarn[rueck])
            rechts_drehen(richtung)
            rechts_drehen(richtung)
        besucht.append(pos)
    return besucht
Dann hast Du ein Verständisproblem, was Variablen und deren Gültigkeit betrifft.
`rechts_drehen` liefert einen Rückgabewert, mit diesem Rückgabewert machst Du aber nichts (bei add_pos zeigst Du ja, wie es funktioniert).
Dann benutzt Du globale Variablen, `besucht`, `raster` etc, was man nicht machen sollte, weil das zu schwer zu findenden Fehlern führen kann. Alles was eine Funktion braucht (außer Konstanten), muß sie über ihre Argumente bekommen.
reb55
User
Beiträge: 8
Registriert: Donnerstag 25. August 2022, 13:09

Danke für Deine Antwort. Die Richtungswahl über die Listen/Prozeduren finde ich chick!
Meine Frage ist damit -zumindest für mich - nicht beantwortet. Ich weiß immer noch nicht warum das Prog brav bis zu sieben Mal das macht was es soll (nämlich an der rechten Wand entlang zu gehen- aber dann auf einmal und für mich nicht nachvollziehbar zwischen der alten und neuen Position hin- und herspringt.
"besucht" und "raster" benötige ich noch in einer anderen Prozedur, deshalb global.
Benutzeravatar
__blackjack__
User
Beiträge: 14031
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@reb55: Wenn Du das noch in anderen Funktionen brauchst, dann übergibst Du denen das halt auch als Argument(e). Kein Grund für globale Variablen.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Sirius3
User
Beiträge: 18266
Registriert: Sonntag 21. Oktober 2012, 17:20

Ein paar Fehler im Code hatte ich ja doch genannt, aber um wirklich Testen zu können, brauchen wir natürlich den vollständigen, lauffähigen Code.
reb55
User
Beiträge: 8
Registriert: Donnerstag 25. August 2022, 13:09

Der Code ist der folgende:
import pygame as pg
import random as rnd


BREITE = HÖHE = 1000
SPALTEN = ZEILEN = 30
ZE_BH = BREITE // SPALTEN


delta_linien = {'l': [(0,0), (0,ZE_BH)], 'r': [(ZE_BH, 0), (ZE_BH, ZE_BH)],
'o': [(0,0), (ZE_BH, 0)], 'u': [(0, ZE_BH), (ZE_BH, ZE_BH)]}
delta_nachbarn = {'l':(-ZE_BH, 0), 'r': (ZE_BH,0), 'o': (0,-ZE_BH), 'u':(0,ZE_BH)}
richtung_invers = {'l':'r', 'r':'l', 'o':'u', 'u':'o'}

pg.init()
screen = pg.display.set_mode([BREITE, HÖHE])
farbe_hintergrund = pg.Color('Black')
farbe_linie = pg.Color('White')
farbe_suche = pg.Color('darkorchid4')



raster = {}
for i in range(SPALTEN*ZEILEN):
pos = i % SPALTEN * ZE_BH, i // SPALTEN * ZE_BH
raster[pos] = {b for b in 'lrou'}

def add_pos(pos1, pos2):
return pos1[0]+pos2[0], pos1[1]+pos2[1]

def zeichne_zelle(pos, wände):
for wand in wände:
delta_von, delta_bis = delta_linien[wand]
von = add_pos(pos,delta_von)
bis = add_pos(pos,delta_bis)
pg.draw.line(screen, farbe_linie, von, bis, 2)

def pg_quit():
for ereignis in pg.event.get():
if ereignis.type == pg.QUIT or \
(ereignis.type == pg.KEYDOWN and ereignis.key == pg.K_ESCAPE):
return True

def nachbarn_ermitteln(pos):
nachb = []
for richtung, delta in delta_nachbarn.items():
neue_pos = add_pos(pos, delta)
if neue_pos not in raster: continue
nachb.append((richtung, neue_pos))
rnd.shuffle(nachb)
return nachb


def labyrinth_erstellen(pos_aktuell, richtung_von):
besucht.add(pos_aktuell)
raster[pos_aktuell].remove(richtung_von)
nachb = nachbarn_ermitteln(pos_aktuell)
for richtung_nach, pos_neu in nachb:
if pos_neu in besucht: continue
raster[pos_aktuell].remove(richtung_nach)
labyrinth_erstellen(pos_neu, richtung_invers[richtung_nach])

def rechts_drehen(richtung):
if richtung == "Ost": return "Sued"
if richtung == "Sued": return "West"
if richtung == "West": return "Nord"
if richtung == "Nord": return "Ost"
def links_drehen(richtung):
if richtung == "Ost": return "Nord"
if richtung == "Sued": return "Ost"
if richtung == "West": return "Sued"
if richtung == "Nord": return "West"

def labyrinth_lösen(pos,richtung):
#ziel = ((SPALTEN)*ZE_BH, (ZEILEN)*ZE_BH)
besucht. append(pos)

for n 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[pos]: # vorne frei
pos = add_pos(pos,delta_nachbarn[vorne])

elif (vorne in raster[pos]) and (rechts not in raster[pos]): # rechts drehen
pos = add_pos(pos,delta_nachbarn[rechts])
rechts_drehen(richtung)

elif (vorne in raster[pos]) and (links not in raster[pos]): #links drehen

pos = add_pos(pos,delta_nachbarn[links])
links_drehen(richtung)

else: #umdrehen
rechts_drehen(richtung)
rechts_drehen(richtung)
pos = add_pos(pos,delta_nachbarn[rueck])

besucht.append(pos)


besucht = set()
labyrinth_erstellen((0,0), 'l')
besucht = []
#position = []
labyrinth_lösen((0,0),'Ost')

while not pg_quit():
screen.fill(farbe_hintergrund)
for pos, wände in raster.items():
zeichne_zelle(pos, wände)
pg.display.flip()

i = 0
while not pg_quit():
pos = besucht
pg.draw.rect(screen, farbe_suche,(pos,(ZE_BH,ZE_BH)))
for pos, wände in raster.items():
zeichne_zelle(pos, wände)
pg.display.flip()
i = min(i+1, len(besucht)-1)



pg.quit()

Ich benutze Spyder-CF.
Benutzeravatar
__blackjack__
User
Beiträge: 14031
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@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()
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
reb55
User
Beiträge: 8
Registriert: Donnerstag 25. August 2022, 13:09

Vielen Dank für Eure Anregungen; es klappt jetzt so wie ich mir das vorstelle. Der (oder besser: die)Fehler lagen
1. wie von Sirius und blackjack erwähnt wurde der Rückgabewert nicht verwendet.
2. der Algorithmus setzt voraus, dass rechts eine Mauer sein muss - egal ob vorne frei ist oder nicht. D.h. selbst wenn vorne keine Mauer ist aber rechts keine Wand muss rechts abgebogen werden.
Ich habe die Anregungen von noch nicht alle umgesetzt; das mache ich als nächstes. Ich finde aber die Schreibweise von blackjack auf jeden Fall besser als meine bisherige.
3. Mag sein dass ich eine Todsünde in Python begehe aber ich habe noch eine dritte (globale) Variable verwendet um die Doppelbelegung von besucht zu beseitigen. Die Anregungen von den globalen Variablen weg zu kommen greife ich aber noch auf. Die funktionale Programmierung ist noch nicht mein Ding.
Die Wegsuche sieht jetzt wie folgt aus (RICHTUNGEN ist gemäß Anregung von sirius gegeben):

def rechts_drehen(richtung):
return RICHTUNGEN[(RICHTUNGEN.index(richtung) + 1) % len(RICHTUNGEN)]

def links_drehen(richtung):
return RICHTUNGEN[(RICHTUNGEN.index(richtung) - 1) % len(RICHTUNGEN)]

def labyrinth_lösen(pos,richtung):
ziel = ((SPALTEN)*ZE_BH, (ZEILEN)*ZE_BH)
weg. append(pos)


for n in range(0,100):

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 rechts in raster[pos] and vorne not in raster[pos]: # vorne frei
pos = add_pos(pos,delta_nachbarn[vorne])

elif rechts not in raster[pos]: # rechts drehen
pos = add_pos(pos,delta_nachbarn[rechts])
richtung = rechts_drehen(richtung)

elif links not in raster[pos]: #links drehen

pos = add_pos(pos,delta_nachbarn[links])
richtung = links_drehen(richtung)

else: #umdrehen
richtung = rechts_drehen(richtung)
richtung = rechts_drehen(richtung)
pos = add_pos(pos,delta_nachbarn[rueck])

weg.append(pos)


besucht = set()
labyrinth_erstellen((0,0), 'l')
weg = []
labyrinth_lösen((0,0),'Ost')
Benutzeravatar
__blackjack__
User
Beiträge: 14031
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Globaler Zustand ist nicht nur in Python eine Todsünde, das macht man in anderen Programmiersprachen auch nicht. ”Gaaanz Früher” hat man das manchmal bis oft aus Performance-Gründen gemacht und weil damals automatisierte Tests und nebenläufige Programmierung noch keine Themen waren (vor allem in Kombination) auf Systemen mit nur einem Prozessor(kern).

Der Code ist unnötig kompliziert weil Du einmal `richtung` als Himmelsrichtung hast, und dann noch mal die eigentlich unnötigen `vorne`, `rechts`, `links`, … und so weiter als Abkürzungen für oben, unten, rechts, links. Was auch verwirrend ist, das links und rechts sowohl links und rechts aus Sicht der aktuellen Ausrichtung des imaginären Läufers durch das Labyrinth bedeuten als auch rechts und links im Kontext der 2D-Karte. Wenn für beides die gleichen Werte benutzt werden, dann wird auch ein bisschen deutlicher was im Code passiert, denn eigentlich ist das ja (ggf.) drehen in die neue Richtung, und dann einen Schritt in diese Richtung gehen. Der letzte Teil ist im Moment in jedem ``if``/``elif``/``else``-Zweig implementiert ohne `richtung` zu erwähnen und das in zwei Zweigen auch in der falschen Reihenfolge gemacht wird. Der Bewegungsschritt müsste aber nur *einmal* nach den ganzen Entscheidungen stehen mit `richtung` als Richtung in die der Schritt gegangen wird.

Der Code würde dann auf so etwas zusammenschrumpfen:

Code: Alles auswählen

def labyrinth_lösen(position, richtung):
    weg = [position]
    for _ in range(100):
        waende = raster[position]
        if rechts_drehen(richtung) in waende and richtung not in waende:
            pass  # Keine Richtungsänderung.
        elif rechts_drehen(richtung) not in waende:
            richtung = rechts_drehen(richtung)
        elif links_drehen(richtung) not in waende:
            richtung = links_drehen(richtung)
        else:
            richtung = rechts_drehen(rechts_drehen(richtung))

        position = add_position(position, delta_nachbarn[richtung])
        weg.append(position)

    return weg
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Sirius3
User
Beiträge: 18266
Registriert: Sonntag 21. Oktober 2012, 17:20

Durch tauschen der if-Bedingungen vereinfacht sich das ganze:

Code: Alles auswählen

def labyrinth_lösen(position, richtung):
    weg = [position]
    for _ in range(100):
        waende = raster[position]
        if rechts_drehen(richtung) not in waende:
            richtung = rechts_drehen(richtung)
        elif richtung not in waende:
            pass  # Keine Richtungsänderung.
        elif links_drehen(richtung) not in waende:
            richtung = links_drehen(richtung)
        else:
            richtung = rechts_drehen(rechts_drehen(richtung))

        position = add_position(position, delta_nachbarn[richtung])
        weg.append(position)

    return weg
Und hier sieht man dann auch, dass man sich am Anfang einmal nach rechts drehen muß, und dann nur noch der Reihe nach immer weiter Links:

Code: Alles auswählen

def labyrinth_lösen(position, richtung):
    weg = [position]
    for _ in range(100):
        waende = raster[position]
        richtung = rechts_drehen(richtung)
        while richtung in waende:
            richtung = links_drehen(richtung)
        position = add_position(position, delta_nachbarn[richtung])
        weg.append(position)
    return weg
Antworten