Game of Life

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
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Ich hab irgend ein Logikfehler in diesem Game of Life.
Die Ausgabe stimmt nicht.

Code: Alles auswählen

#am Rand tote Zellen oder Globusförmiges Spielfeld?
def isTrue(liste,index,unter):
    try:
        if liste[index][unter]==True:
            return True
        elif liste[index][unter]==False:
            return False
    except IndexError:
        return False

def anzahlnachbarn(liste,index,unter):
    listen=[liste,liste,liste,liste,liste,liste,liste,liste]
    indexe=[index-1,index-1,index-1,index,index,index+1,index+1,index+1]
    unterindexe=[unter-1,unter,unter+1,unter-1,unter+1,unter-1,unter,unter+1]
    return len([True for l in listen for i in indexe for u in unterindexe if isTrue(l,i,u)]) 

def neueGeneration(liste):
    neueliste=[]
    temp=[]
    for s in liste:
        for t in liste:
            nachbarn=anzahlnachbarn(liste,liste.index(s),liste.index(t))
            if t:
                if nachbarn<2:
                    temp.append(False)
                elif nachbarn in (2,3):
                    temp.append(True)
                elif nachbarn > 3:
                    temp.append(False)
                                    
            else:
                if nachbarn==3:
                    temp.append(True)
        neueliste.append(temp)
    return neueliste

                    
def main():
    Zeilenanzahl=int(input("Wie viele Zeilen soll ihr Feld haben? "))
    print()
    print("""Bitte geben sie ihr Feld ein. Es soll rechteckig sein.
Ein "x" kennzeichnet eine lebendige Zelle ein "o" eine tote Zelle""")
    Spielfeld=[]
    for i in range(Zeilenanzahl):
        Spielfeld.append(input())

    Spielfeld2=[]
    temp=[]
    for n in Spielfeld:
        for s in n:
            if s=="x":
                temp.append(True)
            elif s=="o":
                temp.append(False)

        Spielfeld2.append(temp)
        temp=[]

    Spielfeld=Spielfeld2

    print(neueGeneration(Spielfeld))
    
    
if __name__ == "__main__":
    main()






            



    


nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

Und? Was stimmt an der Ausgabe nicht?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@fail: schau mal wie oft anzahlNachbarn isTrue aufruft.
wie wird in neueGeneration liste durchlaufen, was passiert mit temp?

Und dann die üblichen Hinweise:
- liste ist kein schöner Name, spielfeld dagegen schon
- x==True oder x==False -Bedingungen werden mit x oder not x geschrieben.
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

immer noch fehler in der logik

Code: Alles auswählen

#am Rand tote Zellen
def isTrue(liste,index,unter):
    try:
        if liste[index][unter]:
            return True
        elif not liste[index][unter]:
            return False
    except IndexError:
        return False

def anzahlnachbarn(liste,index,unter):
    nachbarn=[isTrue(liste,index-1,unter-1),isTrue(liste,index-1,unter),
              isTrue(liste,index-1,unter+1),isTrue(liste,index,unter-1),
              isTrue(liste,index,unter+1),isTrue(liste,index+1,unter-1),
              isTrue(liste,index+1,unter),isTrue(liste,index+1,unter+1)]
    return len([True for l in nachbarn if l]) 

def neueGeneration(liste):
    neueliste=[]
    temp=[]
    index=0
    subindex=0
    while index < len(liste):
        while subindex < len(liste[index]):
            nachbarn=anzahlnachbarn(liste,index,subindex)
            if liste[index][subindex]:
                if nachbarn<2:
                    temp.append(False)
                elif nachbarn in (2,3):
                    temp.append(True)
                elif nachbarn > 3:
                    temp.append(False)
                                    
            else:
                if nachbarn==3:
                    temp.append(True)
            subindex +=1
        index +=1           
        neueliste.append(temp)
        temp=[]
    return neueliste

                    
def main():
    Zeilenanzahl=int(input("Wie viele Zeilen soll ihr Feld haben? "))
    print()
    print("""Bitte geben sie ihr Feld ein. Es soll rechteckig sein.
Ein "x" kennzeichnet eine lebendige Zelle ein "o" eine tote Zelle""")
    Spielfeld=[]
    for i in range(Zeilenanzahl):
        Spielfeld.append(input())

    Spielfeld2=[]
    temp=[]
    for n in Spielfeld:
        for s in n:
            if s=="x":
                temp.append(True)
            elif s=="o":
                temp.append(False)

        Spielfeld2.append(temp)
        temp=[]

    Spielfeld=Spielfeld2

    print(neueGeneration(Spielfeld))
    
    
if __name__ == "__main__":
    main()
    input()






            



    


BlackJack

@fail: Teste doch einfach mal alle Funktionen mit dafür geeigneten Eingaben und vergleiche ob dabei das gewünschte Ergebnis heraus kommt. Dann findest Du schon mal die fehlerhafte(n) Funktione(n). Dabei sollte man mit den Funktionen anfangen die in der Aufrufhierarchie als letztes kommen, also die ihrerseits keine Funktionen verwenden, die Du geschrieben hast. Danach kommen dann die Funktionen, die diese Funktionen verwenden, und so weiter. Ziel ist es das in Teilprobleme zerlegte Gesamtproblem mit Funktionen zu lösen, die jeweils auf getesteten und korrekt arbeitenden Funktionen basieren.

Der Funktionsname `isTrue()` hält sich übrigens nicht an die Namenskonvention und die Funktion ist unnötig kompliziert geschrieben. `liste` enthält ja schon Wahrheitswerte. Und zwar genau die Werte die Du auch zurückgeben willst. Da braucht man also gar nichts für prüfen, sondern man kann den Wert einfach zurück geben. Selbst wenn man etwas prüft ist eine genau entgegengesetzte Bedingung zum ``if`` in einem ``elif`` Zeitverschwendung — dafür gibt es ``else``, ohne das noch einmal eine Prüfung durchgeführt werden muss, deren Ergebnis sowieso schon fest steht. Die Namen könnten auch viel beschreibender für das Problem sein. Man will wissen ob es an den gegebenen Koordinaten in dem Zellen eine lebendige Zelle gibt.

Code: Alles auswählen

def is_alive(cells, x, y):
    try:
        return cells[y][x]
    except IndexError:
        return False
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Wie BlackJack schon geschrieben hat, solltest du Deine Programme in Teilprobleme zerlegen (das ist es, was Programmieren schlussendlich ausmacht). Diese sollten nach Möglichkeit so klein und einfach wie möglich umgesetzt werden - dies macht das Testen einfacher.

"Geht nicht", "Immer noch Fehler", etc. sind keine hilfreichen Informationen. Versuch, deine Problem möglichst exakt und vor allem so zu formulieren, als hätten die Forenteilnehmer keine Ahnung, was das "Game Of Life" ist (für künftige Postings).

Sprich: Was gebe ich meinem Programm/Funktion mit und was erwarte ich als Ergebnis.

Wenn Du an diesem Punkt angekommen bist, sind die meisten Probleme schon keine mehr. :wink:

Ich habe für meine "Game Of Life"-Implementierung einen anderen Ansatz gewählt: Die Welt ist eine Menge (set) von 2-Tupeln, welche die Position der lebendigen Zellen definiert.
Nicht der Stein der Weisen, aber vielleicht kannst du das eine oder andere gebrauchen.

Grüße ... bwbg
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Diese Version funktioniert vielen dank
Was kann ich noch verbessern?

Code: Alles auswählen

#am Rand tote Zellen
def is_alive(cells, y, x):
    try:
        if x>=0 and y>=0:
            return cells[y][x]
    except IndexError:
        return False

def anzahlnachbarn(cells,y,x):
    nachbarn=[is_alive(cells,y-1,x-1),is_alive(cells,y-1,x),
              is_alive(cells,y-1,x+1),is_alive(cells,y,x-1),
              is_alive(cells,y,x+1),is_alive(cells,y+1,x-1),
              is_alive(cells,y+1,x),is_alive(cells,y+1,x+1)]
    return len([True for l in nachbarn if l]) 

def nextGen(cells):
    newCells=[]
    temp=[]
    for y in range(len(cells)):
        for x in range(len(cells[y])):
            nachbarn=anzahlnachbarn(cells,y,x)
            if cells[y][x]:
                if nachbarn<2:
                    temp.append(False)
                elif nachbarn in (2,3):
                    temp.append(True)
                else:
                    temp.append(False)
                                    
            else:
                if nachbarn==3:
                    temp.append(True)
                else:
                    temp.append(False)
        newCells.append(temp)
        temp=[]
    return newCells
def otherBoard(cells):
    cells2=[]
    temp=[]
    for n in cells:
        for s in n:
            if s=="x":
                temp.append(True)
            elif s==".":
                temp.append(False)

        cells2.append(temp)
        temp=[]

    return cells2
                    
def main():
    lines=int(input("Wie viele Zeilen soll ihr Feld haben? "))
    print("\nBitte geben sie ihr Feld ein. Es soll rechteckig sein.")
    print("Ein 'x' kennzeichnet eine lebendige Zelle ein '.' eine tote Zelle")
    cells=[]
    for i in range(lines):
        cells.append(input())
        
    cells=otherBoard(cells)
    antwort="Ja"
    while antwort in ("ja","Ja"):
        print("\n  \n")
        cells=nextGen(cells)
        for y in range(len(cells)):
            for x in cells[y]:
                if cells[y][x]:
                    print("x", end="")
                else:
                    print(".", end="")
            print()

        antwort=input("\nWollen sie noch eine Iteration: Ja / Nein \n ")

    
if __name__ == "__main__":
    main()
    input()
BlackJack

@fail: Die `is_alive()`-Funktion funktioniert nur weil `None` auch „falsch” ist. Darauf sollte man aber nicht stillschweigend bauen, denn von einer Funktion oder Methode deren Name mit `is_*` anfängt, ewartet man in der Regel, dass sie nur `True` und `False` zurück geben kann.

Die Liste mit den vielen Funktionsaufrufen in `anzahlnachbarn()` ist schlechter Stil. Man schreibt nicht den nahezu gleichen Aufruf so oft per Hand (oder kopieren und einfügen) sondern verwendet eine Schleife über die Daten an denen sich die Aufrufe unterscheiden. Die könnte man literal hinschreiben, aber in diesem Falle könnte man sie auch berechnen.

Für den Rückgabewert wird eine Liste erstellt, nur um dann deren Länge zu ermitteln. Das liesse sich effizienter mit `sum()` regeln. Sogar *sehr* einfach und direkt wenn die Liste nur `True` und `False` enthalten würde, denn `bool` ist eine Unterklasse von `int` und `True` und `False` haben die Werte 1 und 0.

In `nextGen()` wird `temp` an ungünstigen Stelle*n* an eine leere Liste gebunden. Man käme mit *einer* Zuweisung im Quelltext aus, wenn man es an der richtigen Stelle macht. Wenn man einen besseren Namen als `temp` vergeben kann, sollte man das auch tun. Hier handelt es sich um eine Zeile einer Art Tabelle, also könnte man es `row` nennen.

Die Berechnung des Folgezustands einer Zelle kann man deutlich kompakter schreiben, wenn man nicht für jede Teilbedingung einen neuen ``elif``-Zweig aufmacht und das Ergebnis der entsprechend formulierten Bedingung an die Liste anhängt, statt literale `True`/`False`-Werte. Ist vielleicht eine gute Übung in boole'schen Ausdrücken das komplett ohne ``if`` zu formulieren, oder zumindest nur mit einem ``if``/``else``.

Der Name `otherBoard()` erschliesst sich mir nicht. Da sollte man etwas beschreibenderes nehmen. Und sich vielleicht dort und auch bei `nextGen()` an die Namenskonventionen halten. Innerhalb der Funktion sind die Namen `n` und `s` nicht wirklich selbsterklärend. Durchnummerieren von Namen ist auch nicht gut. Dann will man in der Regel entweder eine Liste (oder andere Datenstruktur) oder einen passenderen Namen wählen. Hier würde sich `result` als allgemein passender Name anbieten. Die Funktion käme ohne einen Namen dafür aus, wenn man den Code als verschachtelte „list comprehension” kombiniert mit einem bedingten Ausdruck formuliert.

Die Schleife in der Hauptfunktion sollte besser eine ”Endlosschleife” sein, die am Ende bei entsprechender Eingabe abgebrochen wird. Dann muss man `antwort` nicht vor der Schleife definieren. Man könnte es dann vielleicht sogar ohne einen Namen für diesen Wert formulieren.

Bei der Ausgabe der Zellen sind Indizes und direktes iterieren über Sequenzen vermischt, wobei `x` wider erwarten nicht an einen Index gebunden wird wie `y` sondern an einen Zellenwert. Das ist sehr verwirrend. Hier sollte man die unnötigen Indizes weg lassen und bei den Zeilen könnte man mit der `join()`-Methode auf Zeichenketten arbeiten um eine komplette Zeile zu erstellen und auf einmal auszugeben, statt jedes Zeichen einzeln.
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Ich habe glaub ich alles umsetzen können ausser
Die Liste mit den vielen Funktionsaufrufen in `anzahlnachbarn()` ist schlechter Stil. Man schreibt nicht den nahezu gleichen Aufruf so oft per Hand (oder kopieren und einfügen) sondern verwendet eine Schleife über die Daten an denen sich die Aufrufe unterscheiden. Die könnte man literal hinschreiben, aber in diesem Falle könnte man sie auch berechnen.
Wie geht das?


Hier der Code

Code: Alles auswählen

#am Rand tote Zellen

def is_alive(cells, y, x):
    try:
        if x>=0 and y>=0:
            return cells[y][x]
        else:
            return False
    except IndexError:
        return False
    

def anzahlnachbarn(cells,y,x):
    nachbarn=[is_alive(cells,y-1,x-1),is_alive(cells,y-1,x),
              is_alive(cells,y-1,x+1),is_alive(cells,y,x-1),
              is_alive(cells,y,x+1),is_alive(cells,y+1,x-1),
              is_alive(cells,y+1,x),is_alive(cells,y+1,x+1)]
    return sum(nachbarn)


def nextGen(cells):
    newCells=[]
    for y in range(len(cells)):
        row=[]
        for x in range(len(cells[y])):
            nachbarn=anzahlnachbarn(cells,y,x)
            if cells[y][x]:
                row.append(nachbarn in (2,3))                    
            else:
                row.append(nachbarn==3)
        newCells.append(row)
    return newCells


def truefalseBoard(cells):
    result=[]
    row=[]
    for y in cells:
        for x in y:
            row.append(x=="x")
        result.append(row)
        row=[]
    return result

def xdotBoard(row):
    result=[]
    for x in row:
        if x:
            result.append("x")
        else:
            result.append(".")
    return result

                    
def main():
    lines=int(input("Wie viele Zeilen soll ihr Feld haben? "))
    print("\nBitte geben sie ihr Feld ein. Es soll rechteckig sein.")
    print("Ein 'x' kennzeichnet eine lebendige Zelle ein '.' eine tote Zelle")
    cells=[]
    for i in range(lines):
        cells.append(input())
        
    cells=truefalseBoard(cells)
    
    while True:
        print("\n  \n")
        cells=nextGen(cells)
        for y in cells:
            print("".join(xdotBoard(y)))
       
        if not input("\nWollen sie noch eine Iteration: Ja / Nein \n ") in ("ja","Ja"):
            break
            
    
if __name__ == "__main__":
    main()
    input()
BlackJack

@fail: Die Aufrufe unterscheiden sich nur dadurch was zu `x` und `y` addiert wird, also kann man das als Liste von Tupeln heraus ziehen, oder die Werte generieren. Man braucht alle Kombinationen von -1, 0, und 1 für die beiden Additionswerte ausser dass beide 0 sind.

Code: Alles auswählen

def anzahlnachbarn(cells, y, x):
    return sum(
        is_alive(cells, y + dy, x + dx)
        for dx in range(-1, 2) for dy in range(-1, 2))
        if dx or dy
    )
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

diese range(len(...)) sind ja schrecklich, warum hat dazu noch niemand was gesagt?

Code: Alles auswählen

def nextGen(cells):
    return [
        [anzahlnachbarn(cells,y,x) in ((3,),(2,3))[alive] for x, alive in enumerate(row)]
        for y, row in enumerate(cells) ]
@blackjack: bei diesen riesigen Ranges würde ich persönlich (-1, 0, 1) schreiben.
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

@Sirius3
kannst du mir dein code erklären?
Sirius3 hat geschrieben:diese range(len(...)) sind ja schrecklich, warum hat dazu noch niemand was gesagt?

Code: Alles auswählen

def nextGen(cells):
    return [
        [anzahlnachbarn(cells,y,x) in ((3,),(2,3))[alive] for x, alive in enumerate(row)]
        for y, row in enumerate(cells) ]
@blackjack: bei diesen riesigen Ranges würde ich persönlich (-1, 0, 1) schreiben.
BlackJack

@Sirius3: Also entweder das was Du da geschrieben hast funktioniert, dann ist es mir zu komplex, denn ich verstehe es nicht, oder Du versuchst da einen `bool`-Wert mit einer Liste zu Indexieren, das ist nämlich das was ich da lese. Wobei der `bool`-Wert immer `False` sein müsste wenn `anzahlnachbarn()` eine Zahl zurückgibt.

Edit: Und ich habe es falsch gelesen, denn `alive` ist keine Liste. Aber der Ausdruck davor ist IMHO immer noch grundsätzlich `False` und auch `bool` mit `bool` indexieren geht nicht.

Edit2: Jetzt habe ich es verstanden. Aber es ist mir zu komplex und zu leicht falsch zu verstehen.
BlackJack

So hätte ich es einfacher verstanden:

Code: Alles auswählen

def nextGen(cells):
    return [
        [
            anzahlnachbarn(cells, y, x) in ([2, 3] if alive else [3])
            for x, alive in enumerate(row)
        ]
        for y, row in enumerate(cells)
    ]
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Noch einmal ge-list comprehendet

Was kann ich noch verbessern, vor allem bei ein- und ausgabe ; ausser eine GUI zu machen?

Code: Alles auswählen

#am Rand tote Zellen

def is_alive(cells, y, x):
    try:
        return cells[y][x] if x>=0 and y>=0 else False
    except IndexError:
        return False
    
def anzahlnachbarn(cells, y, x):
    return sum([is_alive(cells, y + dy, x + dx) for dx in (-1,0, 1) for dy in (-1, 0, 1) if dx or dy])


def nextGen(cells):
    return [[anzahlnachbarn(cells,y,x) in (2,3) if cells[y][x] else anzahlnachbarn(cells,y,x)==3
            for x in range(len(cells[y]))] for y in range(len(cells))]

#
#   Input and Output
#

def truefalseBoard(cells):
    return [[x=="x" for x in y] for y in cells]

def xdotBoard(row):
    return ["x" if x else "." for x in row]
                    
def main():
    lines=int(input("Wie viele Zeilen soll ihr Feld haben? "))
    print("\nBitte geben sie ihr Feld ein. Es soll rechteckig sein.")
    print("Ein 'x' kennzeichnet eine lebendige Zelle ein '.' eine tote Zelle")
    cells=[]
    for i in range(lines):
        cells.append(input())
        
    cells=truefalseBoard(cells)
    
    while True:
        print("\n  \n")
        cells=nextGen(cells)
        for y in cells:
            print("".join(xdotBoard(y)))
       
        if not input("\nWollen sie noch eine Iteration: Ja / Nein \n ") in ("ja","Ja"):
            break
            
    
if __name__ == "__main__":
    main()
    input()
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

In deiner Ausgabe hast du noch ein paar Rechtschreibfehler, vor allem schreibst du die Höflichkeitsform (Sie, Ihr) klein. Ansonsten ist es schöner, wenn man bei einer for-Schleife, wie der in deiner main()-Funktion,

Code: Alles auswählen

for _ in range(...):
    ...
schreibt, weil du die Variable i gar nicht benutzt, und für diesen Fall hat sich _ so eingebürgert. Außerdem solltest du schreiben:

Code: Alles auswählen

if input().lower() != "ja":
dadurch sparst du dir ein paar Zeichen. Außerdem sollte man, wenn, eher "if input() not in (..., ..., ...):" schreiben, weil "not in" ein eigener Operator ist, und es somit effizienter ist, als die beiden separaten Operatoren "not" und "in" zu verwenden.
BlackJack

@fail: Die „list comprehension” bei `anzahlnachbarn()` kann man als Generatorausdruck schreiben. Also einfach die eckigen Klammern weg lassen.
Antworten