vob thonny nach python3

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
arkturus
User
Beiträge: 2
Registriert: Freitag 20. Dezember 2019, 13:13

Dienstag 24. März 2020, 20:10

Liebe Helfer, ich bin Python-Anfänger.
Hier meine Version von John Conways "Life": Spielfeld; mit li Maustaste beliebige Felder markieren (1.Generation); mit re Maustaste die folgende "Generation" nach 2 Regeln berechnen und anzeigen.
Meine Fragen:
1. Das Spiel läuft gut unter "thonny", nicht aber im Terminal unter "python3". Woran liegts?
2. Die Funktion "augama" (mit Re-Klick neue Generation erzeugen) würde ich gern als Schleife laufen lassen, um die Generationenfolge quasi als Film darzustellen.Mit einer einfachen while-Schleife klappt das nicht (Zeile57, auskommentiert): Es startet eine Endlosschleife, ohne dass sich die Grafik ändert. Habt Ihr mir Tips für die Schleife?

Vielen Dank!
arkturus


#!/usr/bin/env python3
from tkinter import *
feld = Tk()

#feldgrafik:
canvas_width = 900
canvas_height = 900
w = Canvas(feld, bg="green",
width=canvas_width,
height=canvas_height)
w.pack()

for x in range (0,900,20):
w.create_line(x, 0, x,900, fill="#476042")

for y in range (0,900,20):
w.create_line(0, y, 900,y, fill="#476042")

Gmatrix = [] #feldmatrix
Rmatrix = [] #rechenmatrix





for m in range(45):
t = []
for y in range(45):
t.append(0)
Gmatrix.append(t)

for m in range(45):
t = []
for y in range(45):
t.append(0)
Rmatrix.append(t)

def mmove(event): #ausgangslage markieren mit linksklick
global g
global h

i=int(event.x/20)*20 #feldnummer
j=int(event.y/20)*20
h=int(i/20) #matrixnummer,horizontal
g=int(j/20) #vertikal

if Gmatrix[g][h] == 0:
w.create_rectangle(i,j,i+20,j+20,fill="white")
Gmatrix[g][h] = 1
else:
w.create_rectangle(i,j,i+20,j+20,fill="green",outline="#476042" )
Gmatrix[g][h]= 0

feld.bind('<ButtonRelease-1>', mmove)

def augama(event): #spiel startet mit rechtsklick
# while 1<2:
for g in range (44): #anzahl der nachbarn
for h in range (44):
Rmatrix[g][h] = Gmatrix[g+1][h] + Gmatrix[g-1][h] + Gmatrix[g][h+1] + Gmatrix[g][h-1] + Gmatrix[g+1][h+1] + Gmatrix[g-1][h-1] + Gmatrix[g+1][h-1] + Gmatrix[g-1][h+1]

for g in range ( 45):
for h in range (45):

#lebensbedingungen
if Gmatrix[g][h]== 0 :
if Rmatrix[g][h] == 3:
Gmatrix[g][h] = 1
w.create_rectangle(h*20,g*20,(h+1)*20,(g+1)*20,fill="white")

elif Gmatrix[g][h] == 1:
if Rmatrix[g][h] < 2 or Rmatrix[g][h] > 3 :
Gmatrix[g][h] = 0
w.create_rectangle(h*20,g*20,(h+1)*20,(g+1)*20,fill="green",outline="#476042")

feld.bind('<ButtonRelease-3>', augama)
Benutzeravatar
/me
User
Beiträge: 3362
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Dienstag 24. März 2020, 20:59

Das ist so ohne Einrückungen nicht lesbar. Bitte Code auch als Code formatieren.
Benutzeravatar
__blackjack__
User
Beiträge: 6046
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Dienstag 24. März 2020, 21:49

@arkturus: Das läuft nicht weil die GUI-Hauptschleife nicht aufgerufen wird.

Bevor man da etwas erweitert, sollte man das bisherige erst einmal überarbeiten.

Eingerückt wird in Python mit vier Leerzeichen pro Ebene.

Sternchen-Importe sind Böse™. Gerade bei `tkinter` holt man sich damit hunderte von Namen in das Modul von denen nur ein kleiner Bruchteil tatsächlich verwendet wird. Auch welche die gar nicht in `tkinter` definiert werden sondern die `tkinter` selbst von woanders her importiert.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblichweise in einer Funktion die `main()` heisst.

``global`` hat in einem sauberen Programm nichts zu suchen. Die beiden Variablen die in `mmove()` so deklariert werden, werden ausserhalb aber auch gar nicht verwendet‽

Da man keine globalen Variablen verwendet, muss man Funktionen und Methoden alles was sie ausser Konstanten benötigen als Argument(e) übergeben. Bei GUIs läuft das in letzter Konsequenz für jede nicht-triviale GUI auf objektorientierte Programmierung (OOP) hinaus.

Weder `mmove()` noch `augama()` sind sinnvolle Namen weil beide nicht die Tätigkeiten beschreiben die von den Funktionen durchgeführt wird. `toggle_cell()` und `calculate_next_generation()` wären Beschreibungen die deutlich aussagekräftiger wären.

Einbuchstabige Namen sind selten gute Namen. Warum `w` nun ausgerechnet `w` heisst weisst wohl nur Du. Wenn das `canvas` hiesse, wüsste das jeder und auch nicht nur dort wo die Variable definiert wird.

Da sind viele ”magische” Zahlen im Code die in einer bestimmten Abhängigkeit zueinander stehen. Was man aber am Code selbst nicht sieht. Zum Beispiel wie die 900 bei der Canvas-Grösse zustande kommt. Feld- und Zellengrösse sollte man als Konstanten definieren und alle davon abgeleiteten Grössen daraus berechnen. Dann weiss man immer was die Werte bedeuten, auch die daraus ausgerechneten, und man kann beides bei den Konstanten ganz einfach anpassen/verändern.

Die Farben würde man auch als Konstanten definieren.

Statt `Gmatrix` und `Rmatrix` mit Kommentaren zu versehen was das bedeuten soll, sollten die Namen direkt verraten was das bedeuten soll. Dann sind nicht nur die Kommentare überflüssig, sondern man die Bedeutung auch überall ablesen, nicht nur in der Zeile mit dem Kommentar. Wobei auch `feldmatrix` und `rechenmatrix` nicht so wirklich aussagekräftig sind.

Beide Matrizen werden umständlich initialisiert. Mit „list comprehension“ und Multiplikation geht das deutlich kürzer. Die Feldmatrix sollte zudem nicht mit Zahlen befüllt werden, denn die enthält ja eigentlich Wahrheitswerte. Dafür hat Python `True` und `False`.

In `mmove()` sind `i`, `j`, `g`, und `h` wieder schlechte Namen und der Kommentar ``# feldnummer`` irreführend bis falsch. Zudem sind die Namen in einer ungünstigen Reihenfolge definiert, denn `g` und `h` könnte man bei `i` und `j` gut gebrauchen. Gute Namen für `g` und `h` wären `column_index` und `row_index`, und für `i` und `j` würden `x` und `y` passender sein.

Im ``if``- und ``else``-Zweig wird jeweils fast das gleiche gemacht. Da sollte man den gemeinsamen Teil rausziehen um die Codewiederholung zu vermeiden.

Beim Zählen der lebenden Nachbarn sind die `range()`-Grenzen komisch weil asymmetrisch. Oben und links gibt es einen „wrap around“, aber unten und rechts nicht. Der Code ist auch ziemlich viel Handarbeit die man durch zwei, beziehungsweise sogar eine Schleife ersetzen könnte.

Beim anschliessenden auswerten der Nachbaranzahlen gibt es zu einem ``if`` ein unnötiges ``elif`` mit der genau gegenteiligen Bedingung wie beim ``if``. Das ist ein Fall für ein ``else``.

Die beiden innersten Blöcke in dem Konstrukt enthalten wieder fast den gleichen Code. Die Gemeinsamkeiten sollte man auch hier heraus ziehen und nur einmal schreiben. Dann werden auch die Bedingungen für's leben und sterben viel deutlicher denn dann steht nur noch das im ``if``/``else``.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from functools import partial

FIELD_SIZE = 45
CELL_SIZE = 20

CELL_BORDER_COLOR = "#476042"
ALIVE_CELL_COLOR = "white"
DEAD_CELL_COLOR = "green"


def toggle_cell(cell_matrix, canvas, event):
    column_index = int(event.x / CELL_SIZE)
    row_index = int(event.y / CELL_SIZE)
    is_alive = not cell_matrix[row_index][column_index]
    cell_matrix[row_index][column_index] = is_alive

    x = column_index * CELL_SIZE
    y = row_index * CELL_SIZE
    canvas.create_rectangle(
        x,
        y,
        x + CELL_SIZE,
        y + CELL_SIZE,
        fill=ALIVE_CELL_COLOR if is_alive else DEAD_CELL_COLOR,
        outline=CELL_BORDER_COLOR,
    )


def calculate_next_generation(
    neighbour_count_matrix, cell_matrix, canvas, _event
):
    for row_index in range(1, FIELD_SIZE - 1):
        for column_index in range(1, FIELD_SIZE - 1):
            #
            # TODO This is too much code which can be replaced by two or even
            # one ``for`` loop over offsets.
            #
            neighbour_count_matrix[row_index][column_index] = (
                cell_matrix[row_index + 1][column_index]
                + cell_matrix[row_index - 1][column_index]
                + cell_matrix[row_index][column_index + 1]
                + cell_matrix[row_index][column_index - 1]
                + cell_matrix[row_index + 1][column_index + 1]
                + cell_matrix[row_index - 1][column_index - 1]
                + cell_matrix[row_index + 1][column_index - 1]
                + cell_matrix[row_index - 1][column_index + 1]
            )

    for row_index in range(FIELD_SIZE):
        for column_index in range(FIELD_SIZE):
            is_alive = cell_matrix[row_index][column_index]
            neighbour_count = neighbour_count_matrix[row_index][column_index]
            if is_alive:
                is_alive = neighbour_count in [2, 3]
            else:
                is_alive = neighbour_count == 3
            cell_matrix[row_index][column_index] = is_alive
            
            canvas.create_rectangle(
                column_index * CELL_SIZE,
                row_index * CELL_SIZE,
                (column_index + 1) * CELL_SIZE,
                (row_index + 1) * CELL_SIZE,
                fill=ALIVE_CELL_COLOR if is_alive else DEAD_CELL_COLOR,
                outline=CELL_BORDER_COLOR,
            )


def main():
    feld = tk.Tk()
    canvas_size = FIELD_SIZE * CELL_SIZE
    canvas = tk.Canvas(
        feld, bg=DEAD_CELL_COLOR, width=canvas_size, height=canvas_size
    )
    canvas.pack()

    for x in range(0, canvas_size, CELL_SIZE):
        canvas.create_line(x, 0, x, canvas_size, fill=CELL_BORDER_COLOR)

    for y in range(0, canvas_size, CELL_SIZE):
        canvas.create_line(0, y, canvas_size, y, fill=CELL_BORDER_COLOR)

    cell_matrix = [[False] * FIELD_SIZE for _ in range(FIELD_SIZE)]
    neighbour_count_matrix = [[0] * FIELD_SIZE for _ in range(FIELD_SIZE)]

    feld.bind("<ButtonRelease-1>", partial(toggle_cell, cell_matrix, canvas))
    feld.bind(
        "<ButtonRelease-3>",
        partial(
            calculate_next_generation,
            neighbour_count_matrix,
            cell_matrix,
            canvas,
        ),
    )
    feld.mainloop()


if __name__ == "__main__":
    main()
Das ganze hat aber noch einen gravierenden Fehler: `Canvas` ist Vektorgrafik und mit jedem `create_*()` wird ein zusätzliches Grafikobjekt hinzufügt das gezeichnet werden muss und damit das aktualisieren des Fensters immer langsamer und langsamer wird. Man erstellt solche Elemente nur einmal, merkt sich die IDs der erstellten Elemente und ändert dann nur noch die Farbe.

Wobei ich das eh nicht mit einem `Canvas` machen würde sondern mit `Label`-Widgets in einem `grid()`. Dann braucht man sich auch nicht mit den Grafikkordinaten herumschlagen, sondern kann rein bei den Feldkoordinaten bleiben.

Das mit der Animation löst man mittels `after()`-Methode auf Widgets.
long long ago; /* in a galaxy far far away */
arkturus
User
Beiträge: 2
Registriert: Freitag 20. Dezember 2019, 13:13

Mittwoch 25. März 2020, 06:55

Ganz herzlichen Dank, Blackjack, für die Mühe! Jetzt hab ich ja was zum Durcharbeiten für die nächsten Tage ...
Bleib gesund!
Antworten