Erstes Programm: Ein kleines Reaktionsspiel

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

Hi zusammen!

Dies ist mein 2. Posting. Bei einem Problem wurde mir ja schon geholfen, vielen Dank dafür nochmal.

Ich hab mal ne Handelskammer-Ausbildung gemacht als Fachinformatiker (Anwendungsentwicklung). Es war mir bisher jedoch nie vergönnt, als richtiger Programmierer zu arbeiten. Wenn ich mal programmieren durfte, dann höchstens kleinere Scripte zur Automatisierunmg irgendwelcher Listen Transaktionen. Z.B. habe ich - von der oberflächlichen Ausbildung mal abgesehen - null Erfahrung mit OOP und GUI-Programmierung. Ich dachte mir, ich frische meine rudimentären Programmierkenntnisse auf und lerne nebenbei eine neue Sprache (Python). Programmieren tue ich übrigens auf dem Raspberry.

Als erstes Programm habe ich ein kleines Geschicklichkeitsspiel zusammengeschustert. Es ging mir dabei vor allem darum, die elementaren Dinge von Python praktisch zu erproben und das Programmieren mit Fenstern zu erlernen.

Anstatt im nächsten Schritt ein neues Programm anzufangen, habe ich mir gedacht, ich baue den vorhandenen Code komplett um, indem ich ihn in eigene Klassen verpacke. Auch will ich als erstes mal lernen, wie man mit "gutem Stil" programmiert. Das bisher Gemachte könnte vielleicht einen erfahrenen Programmierer zum frösteln bringen. Für Anregungen bin ich natürlich dankbar. Vielleicht knackt ja einer auch meinen Rekord! Hier ist der Code:

Code: Alles auswählen

from tkinter import *
from random import randint
from time import sleep
from _thread import start_new_thread
from pickle import dump
from pickle import load
from os import path

def msg_about():
    messagebox.showinfo("Über das Spiel", "Way2slow\n2016")

def msg_hilfe():
    messagebox.showinfo("Spielregeln",
                        "Starte das Spiel mit der Enter-Taste. " \
                        "Benutze die Pfeiltasten zum Ändern der Nummer. " \
                        "Mit der Leertaste aktivierst Du die sie. " \
                        "Stimmt die Nummer mit der ersten Ziffer in dem " \
                        "schwarzen Display überein, erhälst Du Punkte. " \
                        "Andernfalls verlierst Du ein Leben. Vermeide, dass " \
                        "die Ziffern den linken Rand vom Display erreichen!")

def mache_nichts():
    if not thread_flag: main.destroy()

def abbrechen():
    global thread_flag
    thread_flag=False
    sleep(1)

def beenden(event=None):
    if not thread_flag: main.destroy()

def skin(x):
    global erste_farbe, zweite_farbe, dritte_farbe
    if x=="grau":
        erste_farbe="#D6D7D6"
        zweite_farbe="#969696"
        dritte_farbe="#3B3838"
    if x=="blau":
        erste_farbe="#80D1FF"
        zweite_farbe="#4973CF"
        dritte_farbe="#21345E"       
    if x=="gruen": 
        erste_farbe="#85C489"
        zweite_farbe="#5F8C62"
        dritte_farbe="#2B422B"
    if x=="gelb":
        erste_farbe="#E3EB88"
        zweite_farbe="#ABAD64"
        dritte_farbe="#5B5C35"
    if x=="rot":
        erste_farbe="#FFACAC"
        zweite_farbe="#B57A7A"
        dritte_farbe="#593C39"       
    if x=="lila":
        erste_farbe="#FF00DB"
        zweite_farbe="#7F006D"
        dritte_farbe="#45003B"
    if x=="bunt":
        erste_farbe="#A66AC4"
        zweite_farbe="#A87523"
        dritte_farbe="#32D13E"
    if label_oben_links.cget("text")==0:
        label_oben_links.config(fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_oben_rechts.config(fg=erste_farbe, bg=erste_farbe, relief="flat")
    else:
        label_oben_links.config(bg=zweite_farbe)        
        label_oben_rechts.config(bg=zweite_farbe)
    if label_unten_links.cget("text")==10:
        label_unten_links.config(fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_unten_rechts.config(fg=erste_farbe, bg=erste_farbe, relief="flat")
    else:
        label_unten_links.config(bg=zweite_farbe)        
        label_unten_rechts.config(bg=zweite_farbe)
    main.config(bg=erste_farbe)
    label_mitte_links.config(bg=dritte_farbe)
    label_mitte_rechts.config(bg=dritte_farbe)
    label_punkte.config(bg=erste_farbe)
    label_leben.config(bg=erste_farbe)
    rahmen.config(bg=erste_farbe)
    label_info.config(bg=erste_farbe)

def relief(x):
    global relief_effekt
    if x==1: relief_effekt="flat"
    if x==2: relief_effekt="raised"
    if x==3: relief_effekt="sunken"
    if x==4: relief_effekt="groove"
    if x==5: relief_effekt="ridge"
    if label_oben_links.cget("text")==0:
        label_oben_links.config(relief="flat")
        label_oben_rechts.config(relief="flat")
    else:
        label_oben_links.config(relief=relief_effekt, borderwidth=relief_tiefe)
        label_oben_rechts.config(relief=relief_effekt, borderwidth=relief_tiefe)
    if label_unten_links.cget("text")==10:
        label_unten_links.config(relief="flat")
        label_unten_rechts.config(relief="flat")
    else:
        label_unten_links.config(relief=relief_effekt, borderwidth=relief_tiefe)
        label_unten_rechts.config(relief=relief_effekt, \
                                  borderwidth=relief_tiefe)
    label_mitte_links.config(relief=relief_effekt, borderwidth=relief_tiefe)
    label_mitte_rechts.config(relief=relief_effekt, borderwidth=relief_tiefe)
    label_laufband.config(relief=relief_effekt, borderwidth=relief_tiefe)

def relief_3d(x):
    global relief_tiefe
    if x==1: relief_tiefe=1
    if x==2: relief_tiefe=2
    if x==3: relief_tiefe=3
    if x==4: relief_tiefe=4
    if x==5: relief_tiefe=5
    if x==6: relief_tiefe=6
    label_oben_links.config(borderwidth=relief_tiefe)
    label_oben_rechts.config(borderwidth=relief_tiefe)
    label_mitte_links.config(borderwidth=relief_tiefe)
    label_mitte_rechts.config(borderwidth=relief_tiefe)
    label_unten_links.config(borderwidth=relief_tiefe)
    label_unten_rechts.config(borderwidth=relief_tiefe)
    label_laufband.config(borderwidth=relief_tiefe)
        
def reihe_hoch(event):
    if label_mitte_links.cget("text")==2:
        label_oben_links.config(text=label_oben_links.cget("text")-1,
            fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_oben_rechts.config(text=label_oben_rechts.cget("text")-1,
            fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_mitte_links.config(text=label_mitte_links.cget("text")-1)
        label_mitte_rechts.config(text=label_mitte_rechts.cget("text")-1)
        label_unten_links.config(text=label_unten_links.cget("text")-1)
        label_unten_rechts.config(text=label_unten_rechts.cget("text")-1)
    if label_mitte_links.cget("text")>1:
        label_oben_links.config(text=label_oben_links.cget("text")-1,
            relief=relief_effekt, borderwidth=relief_tiefe)
        label_oben_rechts.config(text=label_oben_rechts.cget("text")-1,
            relief=relief_effekt, borderwidth=relief_tiefe)
        label_mitte_links.config(text=label_mitte_links.cget("text")-1)
        label_mitte_rechts.config(text=label_mitte_rechts.cget("text")-1)
        label_unten_links.config(text=label_unten_links.cget("text")-1,
            fg="White", bg=zweite_farbe, relief=relief_effekt,
            borderwidth=relief_tiefe)
        label_unten_rechts.config(text=label_unten_rechts.cget("text")-1,
            fg="White", bg=zweite_farbe, relief=relief_effekt,
            borderwidth=relief_tiefe)

def reihe_runter(event):
    if label_mitte_links.cget("text")==8:
        label_oben_links.config(text=label_oben_links.cget("text")+1)
        label_oben_rechts.config(text=label_oben_rechts.cget("text")+1)
        label_mitte_links.config(text=label_mitte_links.cget("text")+1)
        label_mitte_rechts.config(text=label_mitte_rechts.cget("text")+1)
        label_unten_links.config(text=label_unten_links.cget("text")+1,
            fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_unten_rechts.config(text=label_unten_rechts.cget("text")+1,
            fg=erste_farbe, bg=erste_farbe, relief="flat")
    if label_mitte_links.cget("text")<9:
        label_oben_links.config(text=label_oben_links.cget("text")+1,
            fg="White", bg=zweite_farbe, relief=relief_effekt,
            borderwidth=relief_tiefe)
        label_oben_rechts.config(text=label_oben_rechts.cget("text")+1,
            fg="White", bg=zweite_farbe, relief=relief_effekt,
            borderwidth=relief_tiefe)
        label_mitte_links.config(text=label_mitte_links.cget("text")+1)
        label_mitte_rechts.config(text=label_mitte_rechts.cget("text")+1)
        label_unten_links.config(text=label_unten_links.cget("text")+1,
            relief=relief_effekt, borderwidth=relief_tiefe)
        label_unten_rechts.config(text=label_unten_rechts.cget("text")+1,
            relief=relief_effekt, borderwidth=relief_tiefe)

# Treffer ermitteln:
def treffer(event):
    global leben, punkte
    if laufband: # Wenn die Liste nicht leer ist, sonst Index-Fehler!
        if label_mitte_links.cget("text")==laufband[0]:
            laufband.pop(0)
            punkte+=level**2
            label_punkte.config(text="Punkte: "+str(format(punkte, ',d')))
            if len(laufband)<5:
                label_laufband.config(text=laufband, anchor=E, fg="White")
            elif len(laufband)>4 and len(laufband)<8:
                label_laufband.config(text=laufband, anchor=E, fg="Yellow")
            else:
                label_laufband.config(text=laufband, anchor=E, fg="#FF6A00")
        else:
            label_laufband.config(text="Autsch !!!", fg="Red", anchor=CENTER)
            if leben>0:
                leben-=1
                label_leben.config(text="noch {} Leben".format(leben))

# Für das Laufband. Läuft im eigenen Thread:
def spiel():
    global thread_flag, leben, punkte, level
    thread_flag=True
    leben=3
    level=1
    sleep_counter=1.3
    main.unbind("<Return>")
    label_laufband.config(text="Los geht's ...   ", fg="White", anchor=E)
    label_leben.config(text="noch {} Leben".format(leben), fg="Black")
    label_punkte.config(text="Punkte: 0")
    while (len(laufband)<10) and thread_flag and leben>0:
        if level <3: skin("grau")
        if level >2 and level <5: skin("blau")
        if level >4 and level <7: skin("gruen")
        if level >6 and level <9: skin("gelb")
        if level >8 and level <11: skin("rot")
        if level >10 and level <13: skin("lila")
        if level >12 and level <15: skin("bunt")
        for i in range(10):
            if (len(laufband)<10) and thread_flag and leben>0:
                sleep(sleep_counter)
                laufband.append(randint(1, 9))
                if len(laufband)<5:
                    label_laufband.config(text=laufband, fg="White", anchor=E)
                elif len(laufband)>4 and len(laufband)<8:
                    label_laufband.config(text=laufband, fg="Yellow", anchor=E)
                else:
                    label_laufband.config(text=laufband, fg="#FF6A00", anchor=E)
        sleep_counter-=0.05
        level+=1
    label_laufband.config(text="GAME OVER", fg="Red", anchor=CENTER)
    try: d=open("highscore.txt", "rb")
    except:
        messagebox.showinfo("Zugriffsfehler", \
                            "Highscore-Datei Zugriff nicht erfolgreich")
        return
    try: highscore_liste=load(d)
    except EOFError:
        d.close()
        return
    d.close()
    # Zweidimensionale Liste sortieren
    highscore_liste=sorted(highscore_liste,key=lambda l:l[1], reverse=True)
    if punkte>highscore_liste[-1][1]:
        speichere_highscore(highscore_liste)
    #Aufräumen für's nächste Spiel:
    laufband.clear()
    leben=3
    punkte=0
    main.bind("<Return>", start)
    thread_flag=False

def speichere_highscore(highscore_liste):
    def ende(event=None):
        highscore_liste[-1][0]=entry_name.get()
        try: d=open("highscore.txt", "wb")
        except:
            messagebox.showinfo("Zugriffsfehler", \
                                "Highscore-Datei Zugriff nicht erfolgreich")
            return
        dump(highscore_liste, d)
        d.close()
        # toplevel_speichern.grab_release() überflüssig
        toplevel_speichern.destroy()
        zeige_highscore()
    toplevel_speichern = Toplevel(main)
    toplevel_speichern.grab_set()
    toplevel_speichern.title("Name")
    toplevel_speichern.resizable(width = 0, height = 0)
    toplevel_speichern.bind("<Return>", ende)
    Label(toplevel_speichern, text= \
          "Du bist in der Top-Ten-Liste!\nDein Name:").grid(row=0, column=0)
    entry_name=Entry(toplevel_speichern)
    entry_name.focus_set()
    entry_name.grid(row=1, column=0)
    Button(toplevel_speichern, text="Ok", command=ende).grid(row=2, column=0)
    highscore_liste[-1][1]=punkte        

def zeige_highscore():
    def string_highscore(l):
        x=""
        for i in range(len(l)):
            if i>8: x+="Platz "+str(i+1)+":   "+l[i][0]+ \
                        " ("+str(format(l[i][1], ',d'))+")\n"
            else:   x+="Platz   "+str(i+1)+":   "+l[i][0]+ \
                        " ("+str(format(l[i][1], ',d'))+")\n"
        return(x)
    try: d=open("highscore.txt", "rb")
    except:
        messagebox.showinfo("Zugriffsfehler", "Dateizugriff nicht erfolgreich")
        return
    try: highscore_liste=load(d)
    except EOFError:
        messagebox.showinfo("Highscores", "Noch keine Highscores vorhanden!")
        d.close()
        return
    d.close()
    # Zweidimensionale Liste sortieren
    highscore_liste=sorted(highscore_liste,key=lambda l:l[1], reverse=True)
    messagebox.showinfo("Highscores", string_highscore(highscore_liste))

def resette_highscores():
    try: d=open("highscore.txt", "wb")
    except:
        messagebox.showinfo("Zugriffsfehler", "Dateizugriff nicht erfolgreich")
        return
    highscore_liste=["Jan", 9810],["Jan", 9279],["Jan", 7231],["Jan", 6501], \
                     ["Jan", 5951],["Jan", 4825],["Jan", 4166],["Jan", 3671], \
                     ["Jan", 3215],["Jan", 2335]
    dump(highscore_liste, d)
    d.close()

def start(event):
    start_new_thread(spiel,())

# Initialisierung:
laufband=[]
thread_flag=False # True = Nebenthread läuft    
leben=3
punkte=0
level=1
erste_farbe="#D6D7D6"
zweite_farbe="#969696"
dritte_farbe="#3B3838"
relief_effekt="groove"
relief_tiefe=2

# Toplevel Widget (Main Window):
main=Tk()
main.protocol('WM_DELETE_WINDOW', mache_nichts)
main.wm_title("Zahlenlaufband")
main.resizable(width=0, height=0)
main.configure(background=erste_farbe)

# Menü:
menubar = Menu(main)
menu_datei = Menu(menubar)
menu_datei.config(tearoff=0)
menu_datei.add_command(label="Zeige Highscores", command=zeige_highscore)
menu_datei.add_command(label="Setze Highscores zurück", \
                       command=resette_highscores)
menu_datei.add_command(label="Beenden", command=beenden)
menubar.add_cascade(label="Datei", menu=menu_datei)
menu_relief = Menu(menubar)
menu_relief.config(tearoff=0)
menu_relief.add_command(label="Flat", command=lambda: relief(1))
menu_relief.add_command(label="Raised", command=lambda: relief(2))
menu_relief.add_command(label="Sunken", command=lambda: relief(3))
menu_relief.add_command(label="Groove", command=lambda: relief(4))
menu_relief.add_command(label="Ridge", command=lambda: relief(5))
menubar.add_cascade(label="Relief-Stil", menu=menu_relief)
menu_relief_3d = Menu(menubar)
menu_relief_3d.config(tearoff=0)
menu_relief_3d.add_command(label="Relief-Tiefe 1",command=lambda: relief_3d(1))
menu_relief_3d.add_command(label="Relief-Tiefe 2",command=lambda: relief_3d(2))
menu_relief_3d.add_command(label="Relief-Tiefe 3",command=lambda: relief_3d(3))
menu_relief_3d.add_command(label="Relief-Tiefe 4",command=lambda: relief_3d(4))
menu_relief_3d.add_command(label="Relief-Tiefe 5",command=lambda: relief_3d(5))
menu_relief_3d.add_command(label="Relief-Tiefe 6",command=lambda: relief_3d(6))
menubar.add_cascade(label="Relief-Tiefe", menu=menu_relief_3d)
menu_hilfe = Menu(menubar)
menu_hilfe.config(tearoff=0)
menu_hilfe.add_command(label="Spielregeln", command=msg_hilfe)
menu_hilfe.add_command(label="Über das Spiel", command=msg_about)
menubar.add_cascade(label="Hilfe", menu=menu_hilfe)
main.config(menu=menubar)

# Row 0:
label_oben_links=Label(main, text=4, width=3, fg="White", relief=relief_effekt,
    borderwidth=relief_tiefe, bg=zweite_farbe, font=("Arial 20"))
label_oben_links.grid(row=0, column=0)
label_punkte=Label(main, bg=erste_farbe, font=("Arial 20"))
label_punkte.grid(row=0, column=1, pady=7)
label_oben_rechts=Label(main, text=4, width=3, fg="White", relief=relief_effekt,
    borderwidth=relief_tiefe, bg=zweite_farbe, font=("Arial 20"))
label_oben_rechts.grid(row=0, column=2)

# Row 1:
label_mitte_links=Label(main, text=5, width=3, relief=relief_effekt,
    borderwidth=relief_tiefe, fg="White", bg=dritte_farbe,
    font=("Arial 30 bold"))
label_mitte_links.grid(row=1, column=0, padx=7)
label_laufband=Label(main, width=13, text="Drücke <Enter>", fg="White",
    bg="Black", font=("Arial 22"), relief=relief_effekt,
    borderwidth=relief_tiefe)
label_laufband.grid(row=1, column=1)
label_mitte_rechts=Label(main, text=5, width=3, relief=relief_effekt,
    borderwidth=relief_tiefe, fg="White", bg=dritte_farbe,
    font=("Arial 30 bold"))
label_mitte_rechts.grid(row=1, column=2, padx=7)

#Row 2:
label_unten_links=Label(main, text=6, width=3, relief=relief_effekt,
    borderwidth=relief_tiefe, fg="White", bg=zweite_farbe, font=("Arial 20"))
label_unten_links.grid(row=2, column=0)
label_leben=Label(main, bg=erste_farbe, font=("Arial 20"))
label_leben.grid(row=2, column=1, pady=7)
label_unten_rechts=Label(main, text=6, width=3, relief=relief_effekt,
    borderwidth=relief_tiefe, fg="White", bg=zweite_farbe, font=("Arial 20"))
label_unten_rechts.grid(row=2, column=2)

# Row 3 (Buttons):
rahmen=Frame(main, bg=erste_farbe)
rahmen.grid(row=3, column=1)
Button(rahmen, text="Abbruch", command=abbrechen).grid(row=0, column=0,
                                                       padx=7, pady=7)
Button(rahmen, text="Ende", command=beenden).grid(row=0, column=1,
                                                  padx=7, pady=7)

# Row 4 (Info-Label):
label_info=Label(main, text="Benutze <Enter>, <Pfeile> und <Leer> zum Spielen",
                 bg=erste_farbe)
label_info.grid(row=4, column=0, columnspan=3)

# Key Bindings:
main.bind("<KeyPress-Up>", reihe_hoch)
main.bind("<KeyPress-Down>", reihe_runter)
main.bind("w", reihe_hoch)
main.bind("s", reihe_runter)
main.bind("<space>", treffer)
main.bind("<Control_L>", treffer)
main.bind("<Control_R>", treffer)
main.bind("<Return>", start)
main.bind("<Tab>", DISABLED)
main.bind("<Escape>", beenden)

# Prüfen, ob Highscore-Datei existiert:
if not path.isfile("highscore.txt"):
    resette_highscores()

main.mainloop()
Zuletzt geändert von way2slow am Montag 15. Februar 2016, 02:33, insgesamt 1-mal geändert.
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

Hmm, wieso ist der Code nicht im Fenster, wie bei anderen Beitraägen hier im Forum? So ist der Code noch nicht mal eingerückt, das macht natürlich wenig Sinn :(

Nachtrag: Ich hab jetzt mal den Tag ausgetauscht. So ist's schon deutlich besser.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@way2slow: nur kurz, von oben nach unten, was mir beim ersten drüberlesen aufgefallen ist:
Zeile 1: *-importe vermeiden, da man nicht kontrollieren kann, was da alles in den eigenenen Namensraum geladen wird. Bei tk üblich "import tkinter as tk", dann kann man alle Tk-Namen einfach z.b. per tk.Label ansprechen.
Zeile 4: _thread ist ein internes Modul (kenntlich am _). Nicht verwenden. Statt dessen threading.
Zeile 7: üblich, nur os zu importieren und als os.path auf die Funktionen zugreifen
Zeile 13ff: wenn etwas in Klammern steht, ist \ unnötig. Umgekehrt sollte man \ vermeiden und statt dessen, wenn nötig Klammern setzen.
Allgemein: global nicht verwenden. Wenn es "globale Zustände" gibt, solltest Du Objektorientiert arbeiten, aber das willst Du ja sowieso tun.
Zeile 35ff: x ist ein schlechter Name, weil er nichts sagt. Benutze immer sprechende Namen. Die if-nicht-Kaskade würde man am besten durch ein Wörterbuch ersetzen.
Zeile 178: benutze format richtig. In Zeile 189 kannst Du's ja auch
Zeile 203ff: es gibt sowas wie elif, damit werden die Bedingungen klarer. Eine Liste mit Levelfarben wäre noch klarer.
Zeile 224: nie nackte except benutzen, da damit auch Programmierfehler (NameError) versteckt werden.
Zeile 229ff: Dateien mit with-Statement öffnen, dann werden sie auch bei Exceptions ordentlich geschlossen.
Zeiel 271: verschachtelte Funktionsdefinitionen sind selten sinnvoll, über den Index einer Liste zu iterieren statt über die Liste selbst, ist schlechtes Python. Wenn Du einen Index zusätzlich brauchst, nimm enumerate, format benutzen! und return ist keine Funktion, also nicht so schreiben als wäre es eine:

Code: Alles auswählen

def string_highscore(highscores):
    return "".join("Platz {0:2d}:   {1} ({2})\n".format(platz, name, punkte)
        for platz, (name, punkte) in highscores)
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

Hey vielen Dank für die ausführliche Hilfe! Ich setz das alles heute abend am Rechner um. Vielleicht meld ich mich zu der einen oder anderen Sache, falls ich was nicht verstehe. Schaut aber soweit alles verständlich aus.
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

@Sirius3:

Ich habe das, was du vorgeschlagen hast, gemacht. Bis auf die Geschichte mit den Globalen Variablen und eigenen Klassen - das scheint mir eine etwas größere Sache zu sein.
Zeile 1: *-importe vermeiden, da man nicht kontrollieren kann, was da alles in den eigenenen Namensraum geladen wird. Bei tk üblich "import tkinter as tk", dann kann man alle Tk-Namen einfach z.b. per tk.Label ansprechen.
Das leuchtet ein.
Zeile 4: _thread ist ein internes Modul (kenntlich am _). Nicht verwenden. Statt dessen threading.
Ok, wusste ich nicht. Offenbar wurde in Python 3 nicht nur der Klassenname in threading geändert, sondern start_new_thread heißt jetzt Thread und wird etwas anders benutzt.
Zeile 7: üblich, nur os zu importieren und als os.path auf die Funktionen zugreifen
Das verstehe ich nicht. Ich hab mir gedacht, es wäre effizient, wenn ich nur eine Klasse oder Funktion von einem Modul verwende, auch nur diese zu importieren.
Zeile 13ff: wenn etwas in Klammern steht, ist \ unnötig. Umgekehrt sollte man \ vermeiden und statt dessen, wenn nötig Klammern setzen.
Ok, danke für den Hinweis.
Zeile 35ff: x ist ein schlechter Name, weil er nichts sagt. Benutze immer sprechende Namen. Die if-nicht-Kaskade würde man am besten durch ein Wörterbuch ersetzen.
Ja, spricht eigentlich nichts gegen einen sprechenden Namen auch an dieser Stelle. Aber welche Stelle meinst du mit "if-nicht-Kaskade"?
Zeile 178: benutze format richtig. In Zeile 189 kannst Du's ja auch
Ja, danke.
Zeile 203ff: es gibt sowas wie elif, damit werden die Bedingungen klarer. Eine Liste mit Levelfarben wäre noch klarer.
Liste für die Farben, könnte ich noch machen. Bzw. später bei größeren Datensammlungen. Macht sicher Sinn für die Übersichtlichkeit und Wartungsfreundlichkeit.
Zu "elif" hab ich aber mal ne Frage: Ich sehe eigentlich nicht den Unterschied zu "if", rein von der Wirkung her. Ob ich nun 1x if und 99x elif schreibe, oder 100x if?
Zeile 224: nie nackte except benutzen, da damit auch Programmierfehler (NameError) versteckt werden. Zeile 229ff: Dateien mit with-Statement öffnen, dann werden sie auch bei Exceptions ordentlich geschlossen.
Das with-Statement war mir bekannt. Ich raffe allerdings noch nicht so richtig, wie man es mit Exceptions kombiniert. Muss ich mich noch mal gesondert mit auseinandersetzen. Aber Danke für den Hinweis.
Zeiel 271: verschachtelte Funktionsdefinitionen sind selten sinnvoll, über den Index einer Liste zu iterieren statt über die Liste selbst, ist schlechtes Python. Wenn Du einen Index zusätzlich brauchst, nimm enumerate, format benutzen! und return ist keine Funktion, also nicht so schreiben als wäre es eine:
Bei der verschachtelten Funktion hab ich wohl um die Ecke gedacht. War unnötig, stimmt. Ist geändert und damit auch der return-Aufruf hinfällig.
Auch der Hinweis bzgl. enumerate war hilfreich.

Ich werd im Anschluss mal den Code vom Ursprungsposting auf den aktuellen Stand bringen, und mich dann die nächsten Tage an die Klassen wagen :D

Edit: Da man ältere Beitraäge scheinbar nicht mehr editieren kann, hier die geänderte Version:

Code: Alles auswählen

import tkinter as tk
from random import randint
from time import sleep
from threading import Thread
from pickle import dump
from pickle import load
from os.path import isfile

def msg_about():
    tk.messagebox.showinfo("Über das Spiel", "Way2slow\n2016")

def msg_hilfe():
    tk.messagebox.showinfo("Spielregeln",
                        "Starte das Spiel mit der Enter-Taste. "
                        "Benutze die Pfeiltasten zum Ändern der Nummer. "
                        "Mit der Leertaste aktivierst Du die sie. "
                        "Stimmt die Nummer mit der ersten Ziffer in dem "
                        "schwarzen Display überein, erhälst Du Punkte. "
                        "Andernfalls verlierst Du ein Leben. Vermeide, dass "
                        "die Ziffern den linken Rand vom Display erreichen!")

def abbrechen():
    global thread_flag
    thread_flag=False
    sleep(1)

def beenden(event=None):
    if not thread_flag: main.destroy()

def skin(farbset):
    global erste_farbe, zweite_farbe, dritte_farbe
    if farbset=="grau":
        erste_farbe="#D6D7D6"
        zweite_farbe="#969696"
        dritte_farbe="#3B3838"
    if farbset=="blau":
        erste_farbe="#80D1FF"
        zweite_farbe="#4973CF"
        dritte_farbe="#21345E"       
    if farbset=="gruen": 
        erste_farbe="#85C489"
        zweite_farbe="#5F8C62"
        dritte_farbe="#2B422B"
    if farbset=="gelb":
        erste_farbe="#E3EB88"
        zweite_farbe="#ABAD64"
        dritte_farbe="#5B5C35"
    if farbset=="rot":
        erste_farbe="#FFACAC"
        zweite_farbe="#B57A7A"
        dritte_farbe="#593C39"       
    if farbset=="lila":
        erste_farbe="#FF00DB"
        zweite_farbe="#7F006D"
        dritte_farbe="#45003B"
    if farbset=="bunt":
        erste_farbe="#A66AC4"
        zweite_farbe="#A87523"
        dritte_farbe="#32D13E"
    if label_oben_links.cget("text")==0:
        label_oben_links.config(fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_oben_rechts.config(fg=erste_farbe, bg=erste_farbe, relief="flat")
    else:
        label_oben_links.config(bg=zweite_farbe)        
        label_oben_rechts.config(bg=zweite_farbe)
    if label_unten_links.cget("text")==10:
        label_unten_links.config(fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_unten_rechts.config(fg=erste_farbe, bg=erste_farbe, relief="flat")
    else:
        label_unten_links.config(bg=zweite_farbe)        
        label_unten_rechts.config(bg=zweite_farbe)
    main.config(bg=erste_farbe)
    label_mitte_links.config(bg=dritte_farbe)
    label_mitte_rechts.config(bg=dritte_farbe)
    label_punkte.config(bg=erste_farbe)
    label_leben.config(bg=erste_farbe)
    rahmen.config(bg=erste_farbe)
    label_info.config(bg=erste_farbe)

def relief(x):
    global relief_effekt
    if x==1: relief_effekt="flat"
    if x==2: relief_effekt="raised"
    if x==3: relief_effekt="sunken"
    if x==4: relief_effekt="groove"
    if x==5: relief_effekt="ridge"
    if label_oben_links.cget("text")==0:
        label_oben_links.config(relief="flat")
        label_oben_rechts.config(relief="flat")
    else:
        label_oben_links.config(relief=relief_effekt, borderwidth=relief_tiefe)
        label_oben_rechts.config(relief=relief_effekt, borderwidth=relief_tiefe)
    if label_unten_links.cget("text")==10:
        label_unten_links.config(relief="flat")
        label_unten_rechts.config(relief="flat")
    else:
        label_unten_links.config(relief=relief_effekt, borderwidth=relief_tiefe)
        label_unten_rechts.config(relief=relief_effekt,
                                  borderwidth=relief_tiefe)
    label_mitte_links.config(relief=relief_effekt, borderwidth=relief_tiefe)
    label_mitte_rechts.config(relief=relief_effekt, borderwidth=relief_tiefe)
    label_laufband.config(relief=relief_effekt, borderwidth=relief_tiefe)

def relief_3d(x):
    global relief_tiefe
    if x==1: relief_tiefe=1
    if x==2: relief_tiefe=2
    if x==3: relief_tiefe=3
    if x==4: relief_tiefe=4
    if x==5: relief_tiefe=5
    if x==6: relief_tiefe=6
    label_oben_links.config(borderwidth=relief_tiefe)
    label_oben_rechts.config(borderwidth=relief_tiefe)
    label_mitte_links.config(borderwidth=relief_tiefe)
    label_mitte_rechts.config(borderwidth=relief_tiefe)
    label_unten_links.config(borderwidth=relief_tiefe)
    label_unten_rechts.config(borderwidth=relief_tiefe)
    label_laufband.config(borderwidth=relief_tiefe)
        
def reihe_hoch(event):
    if label_mitte_links.cget("text")==2:
        label_oben_links.config(text=label_oben_links.cget("text")-1,
            fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_oben_rechts.config(text=label_oben_rechts.cget("text")-1,
            fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_mitte_links.config(text=label_mitte_links.cget("text")-1)
        label_mitte_rechts.config(text=label_mitte_rechts.cget("text")-1)
        label_unten_links.config(text=label_unten_links.cget("text")-1)
        label_unten_rechts.config(text=label_unten_rechts.cget("text")-1)
    if label_mitte_links.cget("text")>1:
        label_oben_links.config(text=label_oben_links.cget("text")-1,
            relief=relief_effekt, borderwidth=relief_tiefe)
        label_oben_rechts.config(text=label_oben_rechts.cget("text")-1,
            relief=relief_effekt, borderwidth=relief_tiefe)
        label_mitte_links.config(text=label_mitte_links.cget("text")-1)
        label_mitte_rechts.config(text=label_mitte_rechts.cget("text")-1)
        label_unten_links.config(text=label_unten_links.cget("text")-1,
            fg="White", bg=zweite_farbe, relief=relief_effekt,
            borderwidth=relief_tiefe)
        label_unten_rechts.config(text=label_unten_rechts.cget("text")-1,
            fg="White", bg=zweite_farbe, relief=relief_effekt,
            borderwidth=relief_tiefe)

def reihe_runter(event):
    if label_mitte_links.cget("text")==8:
        label_oben_links.config(text=label_oben_links.cget("text")+1)
        label_oben_rechts.config(text=label_oben_rechts.cget("text")+1)
        label_mitte_links.config(text=label_mitte_links.cget("text")+1)
        label_mitte_rechts.config(text=label_mitte_rechts.cget("text")+1)
        label_unten_links.config(text=label_unten_links.cget("text")+1,
            fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_unten_rechts.config(text=label_unten_rechts.cget("text")+1,
            fg=erste_farbe, bg=erste_farbe, relief="flat")
    if label_mitte_links.cget("text")<9:
        label_oben_links.config(text=label_oben_links.cget("text")+1,
            fg="White", bg=zweite_farbe, relief=relief_effekt,
            borderwidth=relief_tiefe)
        label_oben_rechts.config(text=label_oben_rechts.cget("text")+1,
            fg="White", bg=zweite_farbe, relief=relief_effekt,
            borderwidth=relief_tiefe)
        label_mitte_links.config(text=label_mitte_links.cget("text")+1)
        label_mitte_rechts.config(text=label_mitte_rechts.cget("text")+1)
        label_unten_links.config(text=label_unten_links.cget("text")+1,
            relief=relief_effekt, borderwidth=relief_tiefe)
        label_unten_rechts.config(text=label_unten_rechts.cget("text")+1,
            relief=relief_effekt, borderwidth=relief_tiefe)

# Treffer ermitteln:
def treffer(event):
    global leben, punkte
    if laufband: # Wenn die Liste nicht leer ist, sonst Index-Fehler!
        if label_mitte_links.cget("text")==laufband[0]:
            laufband.pop(0)
            punkte+=level**2
            label_punkte.config(text="Punkte: {:,d}".format(punkte))
            if len(laufband)<5:
                label_laufband.config(text=laufband, anchor="e", fg="White")
            elif len(laufband)>4 and len(laufband)<8:
                label_laufband.config(text=laufband, anchor="e", fg="Yellow")
            else:
                label_laufband.config(text=laufband, anchor="e", fg="#FF6A00")
        else:
            label_laufband.config(text="Autsch !!!", fg="Red", anchor="center")
            if leben>0:
                leben-=1
                label_leben.config(text="noch {} Leben".format(leben))

# Für das Laufband. Läuft im eigenen Thread:
def spiel():
    global thread_flag, leben, punkte, level
    thread_flag=True
    leben=3
    level=1
    sleep_counter=1.3
    main.unbind("<Return>")
    label_laufband.config(text="Los geht's ...   ", fg="White", anchor="e")
    label_leben.config(text="noch {} Leben".format(leben), fg="Black")
    label_punkte.config(text="Punkte: 0")
    while (len(laufband)<10) and thread_flag and leben>0:
        if level <3: skin("grau")
        elif level >2 and level <5: skin("blau")
        elif level >4 and level <7: skin("gruen")
        elif level >6 and level <9: skin("gelb")
        elif level >8 and level <11: skin("rot")
        elif level >10 and level <13: skin("lila")
        else: skin("bunt")
        for i in range(10):
            if (len(laufband)<10) and thread_flag and leben>0:
                sleep(sleep_counter)
                laufband.append(randint(1, 9))
                if len(laufband)<5:
                    label_laufband.config(text=laufband, fg="White", anchor="e")
                elif len(laufband)>4 and len(laufband)<8:
                    label_laufband.config(text=laufband, fg="Yellow", anchor="e")
                else:
                    label_laufband.config(text=laufband, fg="#FF6A00", anchor="e")
        sleep_counter-=0.05
        level+=1
    label_laufband.config(text="GAME OVER", fg="Red", anchor="center")
    with open("highscore.txt", "rb") as d:
        highscore_liste=load(d)
    # Zweidimensionale Liste sortieren
    highscore_liste=sorted(highscore_liste,key=lambda l:l[1], reverse=True)
    if punkte>highscore_liste[-1][1]:
        speichere_highscore(highscore_liste)
    #Aufräumen für's nächste Spiel:
    laufband.clear()
    leben=3
    punkte=0
    main.bind("<Return>", start)
    thread_flag=False

def speichere_highscore(highscore_liste):
    def ende(event=None):
        highscore_liste[-1][0]=entry_name.get()
        with open("highscore.txt", "wb") as d:
            dump(highscore_liste, d)
        toplevel_speichern.destroy()
        zeige_highscore()
    toplevel_speichern = tk.Toplevel(main)
    toplevel_speichern.grab_set()
    toplevel_speichern.title("Name")
    toplevel_speichern.resizable(width = 0, height = 0)
    toplevel_speichern.bind("<Return>", ende)
    tk.Label(toplevel_speichern, text=
          "Du bist in der Top-Ten-Liste!\nDein Name:").grid(row=0, column=0)
    entry_name=tk.Entry(toplevel_speichern)
    entry_name.focus_set()
    entry_name.grid(row=1, column=0)
    tk.Button(toplevel_speichern, text="Ok", command=ende).grid(row=2, column=0)
    highscore_liste[-1][1]=punkte        

def zeige_highscore():
    with open("highscore.txt", "rb") as d:
        highscore_liste=load(d)
    # Zweidimensionale Liste sortieren
    highscore_liste=sorted(highscore_liste,key=lambda l:l[1], reverse=True)
    highscorestring=""
    for i, a in enumerate(highscore_liste, 1):
        if i!=10:
            highscorestring+="Platz   "+str(i)+":  "+a[0]+" ({:,d})\n" \
                              .format((a[1]))
        else:
            highscorestring+="Platz "+str(i)+":  "+a[0]+" ({:,d})\n" \
                              .format((a[1]))
    tk.messagebox.showinfo("Highscores", highscorestring)

def resette_highscores():
    with open("highscore.txt", "wb") as d:
        highscore_liste=["Jan", 9810],["Jan", 9279],["Jan", 7231], \
                         ["Jan", 6501],["Jan", 5951],["Jan", 4825], \
                         ["Jan", 4166],["Jan", 3671],["Jan", 3215],["Jan", 2335]
        dump(highscore_liste, d)

def start(event):
    Thread(target=spiel).start()

# Initialisierung:
laufband=[]
thread_flag=False # True = Nebenthread läuft    
leben=3
punkte=0
level=1
erste_farbe="#D6D7D6"
zweite_farbe="#969696"
dritte_farbe="#3B3838"
relief_effekt="groove"
relief_tiefe=2

# Toplevel Widget (Main Window):
main=tk.Tk()
main.protocol('WM_DELETE_WINDOW', beenden)
main.wm_title("Zahlenlaufband")
main.resizable(width=0, height=0)
main.configure(background=erste_farbe)

# Menü:
menubar = tk.Menu(main)
menu_datei = tk.Menu(menubar)
menu_datei.config(tearoff=0)
menu_datei.add_command(label="Zeige Highscores", command=zeige_highscore)
menu_datei.add_command(label="Setze Highscores zurück",
                       command=resette_highscores)
menu_datei.add_command(label="Beenden", command=beenden)
menubar.add_cascade(label="Datei", menu=menu_datei)
menu_relief = tk.Menu(menubar)
menu_relief.config(tearoff=0)
menu_relief.add_command(label="Flat", command=lambda: relief(1))
menu_relief.add_command(label="Raised", command=lambda: relief(2))
menu_relief.add_command(label="Sunken", command=lambda: relief(3))
menu_relief.add_command(label="Groove", command=lambda: relief(4))
menu_relief.add_command(label="Ridge", command=lambda: relief(5))
menubar.add_cascade(label="Relief-Stil", menu=menu_relief)
menu_relief_3d = tk.Menu(menubar)
menu_relief_3d.config(tearoff=0)
menu_relief_3d.add_command(label="Relief-Tiefe 1",command=lambda: relief_3d(1))
menu_relief_3d.add_command(label="Relief-Tiefe 2",command=lambda: relief_3d(2))
menu_relief_3d.add_command(label="Relief-Tiefe 3",command=lambda: relief_3d(3))
menu_relief_3d.add_command(label="Relief-Tiefe 4",command=lambda: relief_3d(4))
menu_relief_3d.add_command(label="Relief-Tiefe 5",command=lambda: relief_3d(5))
menu_relief_3d.add_command(label="Relief-Tiefe 6",command=lambda: relief_3d(6))
menubar.add_cascade(label="Relief-Tiefe", menu=menu_relief_3d)
menu_hilfe = tk.Menu(menubar)
menu_hilfe.config(tearoff=0)
menu_hilfe.add_command(label="Spielregeln", command=msg_hilfe)
menu_hilfe.add_command(label="Über das Spiel", command=msg_about)
menubar.add_cascade(label="Hilfe", menu=menu_hilfe)
main.config(menu=menubar)

# Row 0:
label_oben_links=tk.Label(main, text=4, width=3, fg="White", relief=relief_effekt,
    borderwidth=relief_tiefe, bg=zweite_farbe, font=("Arial 20"))
label_oben_links.grid(row=0, column=0)
label_punkte=tk.Label(main, bg=erste_farbe, font=("Arial 20"))
label_punkte.grid(row=0, column=1, pady=7)
label_oben_rechts=tk.Label(main, text=4, width=3, fg="White", relief=relief_effekt,
    borderwidth=relief_tiefe, bg=zweite_farbe, font=("Arial 20"))
label_oben_rechts.grid(row=0, column=2)

# Row 1:
label_mitte_links=tk.Label(main, text=5, width=3, relief=relief_effekt,
    borderwidth=relief_tiefe, fg="White", bg=dritte_farbe,
    font=("Arial 30 bold"))
label_mitte_links.grid(row=1, column=0, padx=7)
label_laufband=tk.Label(main, width=13, text="Drücke <Enter>", fg="White",
    bg="Black", font=("Arial 22"), relief=relief_effekt,
    borderwidth=relief_tiefe)
label_laufband.grid(row=1, column=1)
label_mitte_rechts=tk.Label(main, text=5, width=3, relief=relief_effekt,
    borderwidth=relief_tiefe, fg="White", bg=dritte_farbe,
    font=("Arial 30 bold"))
label_mitte_rechts.grid(row=1, column=2, padx=7)

#Row 2:
label_unten_links=tk.Label(main, text=6, width=3, relief=relief_effekt,
    borderwidth=relief_tiefe, fg="White", bg=zweite_farbe, font=("Arial 20"))
label_unten_links.grid(row=2, column=0)
label_leben=tk.Label(main, bg=erste_farbe, font=("Arial 20"))
label_leben.grid(row=2, column=1, pady=7)
label_unten_rechts=tk.Label(main, text=6, width=3, relief=relief_effekt,
    borderwidth=relief_tiefe, fg="White", bg=zweite_farbe, font=("Arial 20"))
label_unten_rechts.grid(row=2, column=2)

# Row 3 (Buttons):
rahmen=tk.Frame(main, bg=erste_farbe)
rahmen.grid(row=3, column=1)
tk.Button(rahmen, text="Abbruch", command=abbrechen).grid(row=0, column=0,
                                                       padx=7, pady=7)
tk.Button(rahmen, text="Ende", command=beenden).grid(row=0, column=1,
                                                  padx=7, pady=7)

# Row 4 (Info-Label):
label_info=tk.Label(main, text="Benutze <Enter>, <Pfeile> und <Leer> zum Spielen",
                 bg=erste_farbe)
label_info.grid(row=4, column=0, columnspan=3)

# Key Bindings:
main.bind("<KeyPress-Up>", reihe_hoch)
main.bind("<KeyPress-Down>", reihe_runter)
main.bind("w", reihe_hoch)
main.bind("s", reihe_runter)
main.bind("<space>", treffer)
main.bind("<Control_L>", treffer)
main.bind("<Control_R>", treffer)
main.bind("<Return>", start)
main.bind("<Tab>", tk.DISABLED)
main.bind("<Escape>", beenden)

# Prüfen, ob Highscore-Datei existiert:
if not isfile("highscore.txt"):
    resette_highscores()

main.mainloop()
BlackJack

@way2slow: Das `threading`-Modul gab es auch schon in Python 2 und das sollte man auch dort schon seit Jahren verwenden. Wie bist Du denn überhaupt auf `_thread` gekommen?

Wenn Du `path` aus `os` importierst wird ja trotzdem das `os`-Modul ausgeführt, denn sonst gäbe es dort das `path`-Attribut nicht. Und ob man nun den Namen `os` oder den Namen `path` direkt im eigenen Modul hat, macht fast keinen Unterschied. Ausser dass man dann besser keine lokale Variable mehr `path` nennen sollte, damit es nicht verwirrend wird.

Wobei man den `isfile()`-Test an der Stelle eher sowieso nicht verwenden würde. Man würde einfach versuchen die Highscore-Datei einzulesen und wenn das nicht klappt wegen einer Ausnahme, die Daten auf die Standardwerte setzen (ohne sie gleich zu speichern). Das deckt dann nicht nur ab das der Pfad existiert und eine Datei ist, sondern auch eventuelle Rechteprobleme oder Probleme mit dem Inhalt der Datei, wenn man die passenden Ausnahmen angibt. Anstelle von `pickle` würde ich dort übrigens eher auf JSON setzen, oder vielleicht auch CSV. Das sind Formate die nicht von Python abhängen.

Diese ganzen ``if x == …`` könnte man jeweils durch Wörterbücher oder Listen ersetzen. Und bei `relief_tiefe` schau Dir mal `x` an und den Wert auf den `relief_tiefe` gesetzt wird.

Der Unterschied zwischen mehreren ``if``\s und ``if`` gefolgt von ``elif``\s ist das die Bedingung für jedes ``if`` ausgeführt wird, bei ``elif`` nur so lange bis eines gefunden wurde das eine wahre Bedingung hat. Und es macht dem Leser deutlich das sich die Zweige ausschliessen sollen.
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

BlackJack hat geschrieben:@way2slow: Das `threading`-Modul gab es auch schon in Python 2 und das sollte man auch dort schon seit Jahren verwenden. Wie bist Du denn überhaupt auf `_thread` gekommen?
Hallo BlackJack, vorab vielen Dank für Deine Hilfe.

Ich hab mir das Buch "Einstieg in Python" von Thomas Theis (3. Auflage) gekauft. Da steht das mit "_thread" auf Seite 230. Hat ja auch gut geklappt, auch, wenn's unüblich ist.
BlackJack hat geschrieben:Wenn Du `path` aus `os` importierst wird ja trotzdem das `os`-Modul ausgeführt, denn sonst gäbe es dort das `path`-Attribut nicht. Und ob man nun den Namen `os` oder den Namen `path` direkt im eigenen Modul hat, macht fast keinen Unterschied. Ausser dass man dann besser keine lokale Variable mehr `path` nennen sollte, damit es nicht verwirrend wird.
Ich glaub, so ganz verstehe ich das Import-System von Python noch nicht. Also, wenn Dich das richtig verstanden habe, dann werden bei "import modulname" alle Klassen des Moduls importiert, und ich muss auf die einzelnen Klassen mit "modulname.Klasse" zugreifen und kann alles Klasen des Moduls so ansprechen. Wenn ich hingegen nur eine Klasse des Moduls mit "import Klasse from modulname" importiere, dass wird trotzdem alles von diesem Modul geladen? Und ich kann nur auf diese eine Klasse zufreifen, jedoch ohne den Modulnamen vorher zu schreiben.
BlackJack hat geschrieben:Wobei man den `isfile()`-Test an der Stelle eher sowieso nicht verwenden würde. Man würde einfach versuchen die Highscore-Datei einzulesen und wenn das nicht klappt wegen einer Ausnahme, die Daten auf die Standardwerte setzen (ohne sie gleich zu speichern). Das deckt dann nicht nur ab das der Pfad existiert und eine Datei ist, sondern auch eventuelle Rechteprobleme oder Probleme mit dem Inhalt der Datei, wenn man die passenden Ausnahmen angibt.
Ok. Das sind so Sachen, die verstehe ich zwar nicht 100%, weil mir schien die von mir gefundene Lösung simpel und wirksam. Aber genau wegen solcher Tipps habe euch ja auch um Hilfe gefragt. Übrigens war es gewollt, dass die Highscore-Datei vom Programm erzeugt wird, falls sie nicht existiert. Das hat den Vorteil, dass, wenn ich das Spiel mal jemandem maile oder so, ich nur die eine Datei mit dem Quellcode weitergeben muss.
BlackJack hat geschrieben:Anstelle von `pickle` würde ich dort übrigens eher auf JSON setzen, oder vielleicht auch CSV. Das sind Formate die nicht von Python abhängen.
Das lass ich mir nachher mal in Ruhe durch den Kopf gehen. Ich fand die die Geschichte mit "pickle.load" bzw. "pickle.dump" sinnvoll, weil man eine Highscore-Datei ja nicht editieren soll. Das wäre schummeln. Nicht, dass man das nicht anderweitig könnte :D
BlackJack hat geschrieben:Diese ganzen ``if x == …`` könnte man jeweils durch Wörterbücher oder Listen ersetzen. Und bei `relief_tiefe` schau Dir mal `x` an und den Wert auf den `relief_tiefe` gesetzt wird.
Ein wertvoller Hinweis, besonders da mir jetzt klart ist, dass diese ganzen if's völlig überflüssig waren. Da sieht man wohl meine geringe Programmier-Erfahrung.
BlackJack hat geschrieben:Der Unterschied zwischen mehreren ``if``\s und ``if`` gefolgt von ``elif``\s ist das die Bedingung für jedes ``if`` ausgeführt wird, bei ``elif`` nur so lange bis eines gefunden wurde das eine wahre Bedingung hat. Und es macht dem Leser deutlich das sich die Zweige ausschliessen sollen.
Wohl war. Jetzt, wo ich darüber nachdenke - hast Recht.

Nochmals danke für die Hilfe!
BlackJack

@way2slow: Wenn Du ein Modul importierst wird das ausgeführt. Linear von oben nach unten. Erst danach sind die Namen ja überhaupt erst an die Werte gebunden die durch den Quelltext definiert werden. Dementsprechend muss natürlich auch das gesamte Modul ausgeführt werden wenn man gezielt nur einen einzelnen Namen aus dem Modul importiert, denn bevor das Modul nicht ausgeführt wurde, gibt es auch diesen einzelnen Namen nicht.

Das die Highscoredatei vom Programm erzeugt wird ist ja okay, nur ist der Zeitpunkt nicht der günstigste. Beim laden zu speichern ist auch ein bisschen widersinnig. Wenn man die Punktetabelle nicht laden kann, dann fängt man einfach mit der leeren bzw. der Standardtabelle an ohne sie gleich zu speichern. Denn wenn der Spieler seinen Punktestand am Ende dort verewigt, *dann* muss ja sowieso gespeichert werden. Darum muss man zwischendurch/am Anfang nicht unbedingt die leere Tabelle speichern.
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

Ok, was du sagst, ist sauberer. Mein Gedanke war, ich greife an mehreren Stellen auf die Highscore-Datei zu, also checke ich gleich zu Beginn schon mal provisorisch, ob sie da ist in leg sie sonst an. Dann brauch ich mich nicht an mehreren Stellen drum kümmern, was passiert, wenn sie nicht da ist.

Bevor ich mich an die OOP-Version ranwage, werd ich die nächsten Tage erst mal meinen Raspberry mit SSH fernsteuern lernen. Ich will nämlich ein paar Sachen mit der Kamera anstellen, und da ist es unpraktisch, wenn der Monitor und das andere Gestrüpp da dranhängt.

Aber hinterher geht's weiter mit dem Spiel.

Danke nochmal für alle Antorten!

Aktuelle Version:

Code: Alles auswählen

import tkinter as tk
import random
import time
import threading
import pickle
import os

def msg_about():
    tk.messagebox.showinfo("Über das Spiel", "Way2slow\n2016")

def msg_hilfe():
    tk.messagebox.showinfo("Spielregeln",
                        "Starte das Spiel mit der Enter-Taste. "
                        "Benutze die Pfeiltasten zum Ändern der Nummer. "
                        "Mit der Leertaste aktivierst Du die sie. "
                        "Stimmt die Nummer mit der ersten Ziffer in dem "
                        "schwarzen Display überein, erhälst Du Punkte. "
                        "Andernfalls verlierst Du ein Leben. Vermeide, dass "
                        "die Ziffern den linken Rand vom Display erreichen!")

def abbrechen(event=None):
    global thread_flag
    thread_flag=False
    time.sleep(1)

def beenden(event=None):
    if not thread_flag: main.destroy()

def skin(farbset):
    global erste_farbe, zweite_farbe, dritte_farbe
    alle_farbsets={"grau":("#D6D7D6","#969696","#3B3838"),
                   "blau":("#80D1FF","#4973CF","#21345E"),
                   "gruen":("#85C489","#5F8C62","#2B422B"),
                   "gelb":("#E3EB88","#ABAD64","#5B5C35"),
                   "rot":("#FFACAC","#B57A7A","#593C39"),
                   "lila":("#FF00DB","#7F006D","#45003B"),
                   "bunt":("#A66AC4","#A87523","#32D13E")}
    erste_farbe=alle_farbsets[farbset][0]
    zweite_farbe=alle_farbsets[farbset][1]
    dritte_farbe=alle_farbsets[farbset][2]
    if label_oben_links.cget("text")==0:
        label_oben_links.config(fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_oben_rechts.config(fg=erste_farbe, bg=erste_farbe, relief="flat")
    else:
        label_oben_links.config(bg=zweite_farbe)        
        label_oben_rechts.config(bg=zweite_farbe)
    if label_unten_links.cget("text")==10:
        label_unten_links.config(fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_unten_rechts.config(fg=erste_farbe, bg=erste_farbe, relief="flat")
    else:
        label_unten_links.config(bg=zweite_farbe)        
        label_unten_rechts.config(bg=zweite_farbe)
    main.config(bg=erste_farbe)
    label_mitte_links.config(bg=dritte_farbe)
    label_mitte_rechts.config(bg=dritte_farbe)
    label_punkte.config(bg=erste_farbe)
    label_leben.config(bg=erste_farbe)
    rahmen.config(bg=erste_farbe)
    label_info.config(bg=erste_farbe)

def relief(effekt):
    global relief_effekt
    relief_effekt=effekt
    if label_oben_links.cget("text")==0:
        label_oben_links.config(relief="flat")
        label_oben_rechts.config(relief="flat")
    else:
        label_oben_links.config(relief=relief_effekt, borderwidth=relief_tiefe)
        label_oben_rechts.config(relief=relief_effekt, borderwidth=relief_tiefe)
    if label_unten_links.cget("text")==10:
        label_unten_links.config(relief="flat")
        label_unten_rechts.config(relief="flat")
    else:
        label_unten_links.config(relief=relief_effekt, borderwidth=relief_tiefe)
        label_unten_rechts.config(relief=relief_effekt,
                                  borderwidth=relief_tiefe)
    label_mitte_links.config(relief=relief_effekt, borderwidth=relief_tiefe)
    label_mitte_rechts.config(relief=relief_effekt, borderwidth=relief_tiefe)
    label_laufband.config(relief=relief_effekt, borderwidth=relief_tiefe)

def relief_3d(tiefen_wert):
    global relief_tiefe
    relief_tiefe=tiefen_wert
    label_oben_links.config(borderwidth=relief_tiefe)
    label_oben_rechts.config(borderwidth=relief_tiefe)
    label_mitte_links.config(borderwidth=relief_tiefe)
    label_mitte_rechts.config(borderwidth=relief_tiefe)
    label_unten_links.config(borderwidth=relief_tiefe)
    label_unten_rechts.config(borderwidth=relief_tiefe)
    label_laufband.config(borderwidth=relief_tiefe)
        
def reihe_hoch(event):
    if label_mitte_links.cget("text")==2:
        label_oben_links.config(text=label_oben_links.cget("text")-1,
            fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_oben_rechts.config(text=label_oben_rechts.cget("text")-1,
            fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_mitte_links.config(text=label_mitte_links.cget("text")-1)
        label_mitte_rechts.config(text=label_mitte_rechts.cget("text")-1)
        label_unten_links.config(text=label_unten_links.cget("text")-1)
        label_unten_rechts.config(text=label_unten_rechts.cget("text")-1)
    elif label_mitte_links.cget("text")>1:
        label_oben_links.config(text=label_oben_links.cget("text")-1,
            relief=relief_effekt, borderwidth=relief_tiefe)
        label_oben_rechts.config(text=label_oben_rechts.cget("text")-1,
            relief=relief_effekt, borderwidth=relief_tiefe)
        label_mitte_links.config(text=label_mitte_links.cget("text")-1)
        label_mitte_rechts.config(text=label_mitte_rechts.cget("text")-1)
        label_unten_links.config(text=label_unten_links.cget("text")-1,
            fg="White", bg=zweite_farbe, relief=relief_effekt,
            borderwidth=relief_tiefe)
        label_unten_rechts.config(text=label_unten_rechts.cget("text")-1,
            fg="White", bg=zweite_farbe, relief=relief_effekt,
            borderwidth=relief_tiefe)

def reihe_runter(event):
    if label_mitte_links.cget("text")==8:
        label_oben_links.config(text=label_oben_links.cget("text")+1)
        label_oben_rechts.config(text=label_oben_rechts.cget("text")+1)
        label_mitte_links.config(text=label_mitte_links.cget("text")+1)
        label_mitte_rechts.config(text=label_mitte_rechts.cget("text")+1)
        label_unten_links.config(text=label_unten_links.cget("text")+1,
            fg=erste_farbe, bg=erste_farbe, relief="flat")
        label_unten_rechts.config(text=label_unten_rechts.cget("text")+1,
            fg=erste_farbe, bg=erste_farbe, relief="flat")
    elif label_mitte_links.cget("text")<9:
        label_oben_links.config(text=label_oben_links.cget("text")+1,
            fg="White", bg=zweite_farbe, relief=relief_effekt,
            borderwidth=relief_tiefe)
        label_oben_rechts.config(text=label_oben_rechts.cget("text")+1,
            fg="White", bg=zweite_farbe, relief=relief_effekt,
            borderwidth=relief_tiefe)
        label_mitte_links.config(text=label_mitte_links.cget("text")+1)
        label_mitte_rechts.config(text=label_mitte_rechts.cget("text")+1)
        label_unten_links.config(text=label_unten_links.cget("text")+1,
            relief=relief_effekt, borderwidth=relief_tiefe)
        label_unten_rechts.config(text=label_unten_rechts.cget("text")+1,
            relief=relief_effekt, borderwidth=relief_tiefe)

# Treffer ermitteln:
def treffer(event):
    global leben, punkte
    if laufband: # Wenn die Liste nicht leer ist, sonst Index-Fehler!
        if label_mitte_links.cget("text")==laufband[0]:
            laufband.pop(0)
            punkte+=level**2
            label_punkte.config(text="Punkte: {:,d}".format(punkte))
            if len(laufband)<5:
                label_laufband.config(text=laufband, anchor="e", fg="White")
            elif len(laufband)>4 and len(laufband)<8:
                label_laufband.config(text=laufband, anchor="e", fg="Yellow")
            else:
                label_laufband.config(text=laufband, anchor="e", fg="#FF6A00")
        else:
            label_laufband.config(text="Autsch !!!", fg="Red", anchor="center")
            if leben>0:
                leben-=1
                label_leben.config(text="noch {} Leben".format(leben))

# Für das Laufband. Läuft im eigenen Thread:
def spiel():
    global thread_flag, leben, punkte, level
    thread_flag=True
    leben=3
    level=1
    sleep_counter=1.3
    main.unbind("<Return>")
    label_laufband.config(text="Los geht's ...   ", fg="White", anchor="e")
    label_leben.config(text="noch {} Leben".format(leben), fg="Black")
    label_punkte.config(text="Punkte: 0")
    while (len(laufband)<10) and thread_flag and leben>0:
        if level <3: skin("grau")
        elif level >2 and level <5: skin("blau")
        elif level >4 and level <7: skin("gruen")
        elif level >6 and level <9: skin("gelb")
        elif level >8 and level <11: skin("rot")
        elif level >10 and level <13: skin("lila")
        else: skin("bunt")
        for i in range(10):
            if (len(laufband)<10) and thread_flag and leben>0:
                time.sleep(sleep_counter)
                laufband.append(random.randint(1, 9))
                if len(laufband)<5:
                    label_laufband.config(text=laufband, fg="White", anchor="e")
                elif len(laufband)>4 and len(laufband)<8:
                    label_laufband.config(text=laufband, fg="Yellow", anchor="e")
                else:
                    label_laufband.config(text=laufband, fg="#FF6A00", anchor="e")
        sleep_counter-=0.05
        level+=1
    label_laufband.config(text="GAME OVER", fg="Red", anchor="center")
    try:
        with open("highscore.txt", "rb") as d:
            highscore_liste=pickle.load(d)
    except FileNotFoundError:
        highscore_liste=setze_default_highscores()
    # Zweidimensionale Liste sortieren
    highscore_liste=sorted(highscore_liste,key=lambda l:l[1], reverse=True)
    if punkte>highscore_liste[-1][1]:
        speichere_highscores(highscore_liste)
    #Aufräumen für's nächste Spiel:
    laufband.clear()
    leben=3
    punkte=0
    main.bind("<Return>", start)
    thread_flag=False

def speichere_highscores(highscore_liste):
    def ende(event=None):
        highscore_liste[-1][0]=entry_name.get()
        try:
            with open("highscore.txt", "wb") as d:
                pickle.dump(highscore_liste, d)
        except FileNotFoundError:
            resette_highscores()
            return
        toplevel_speichern.destroy()
        zeige_highscores()
    toplevel_speichern = tk.Toplevel(main)
    toplevel_speichern.grab_set()
    toplevel_speichern.title("Name")
    toplevel_speichern.resizable(width = 0, height = 0)
    toplevel_speichern.bind("<Return>", ende)
    tk.Label(toplevel_speichern, text=
          "Du bist in der Top-Ten-Liste!\nDein Name:").grid(row=0, column=0)
    entry_name=tk.Entry(toplevel_speichern)
    entry_name.focus_set()
    entry_name.grid(row=1, column=0)
    tk.Button(toplevel_speichern, text="Ok", command=ende).grid(row=2, column=0)
    highscore_liste[-1][1]=punkte        

def zeige_highscores():
    try:
        with open("highscore.txt", "rb") as d:
                highscore_liste=pickle.load(d)
    except FileNotFoundError:
        highscore_liste=setze_default_highscores()
    # Zweidimensionale Liste sortieren
    highscore_liste=sorted(highscore_liste,key=lambda l:l[1], reverse=True)
    highscorestring=""
    for i, a in enumerate(highscore_liste, 1):
        if i!=10:
            highscorestring+="Platz   "+str(i)+":  "+a[0]+" ({:,d})\n" \
                              .format((a[1]))
        else:
            highscorestring+="Platz "+str(i)+":  "+a[0]+" ({:,d})\n" \
                              .format((a[1]))
    tk.messagebox.showinfo("Highscores", highscorestring)

def setze_default_highscores():
    tk.messagebox.showinfo("Highscores", "Benutze Default-Highscores")
    highscore_liste=["Jan", 9810],["Jan", 9279],["Jan", 7231], \
                    ["Jan", 6501],["Jan", 5951],["Jan", 4825], \
                    ["Jan", 4166],["Jan", 3671],["Jan", 3215],["Jan", 2335]
    return highscore_liste

def resette_highscores():
    highscore_liste=setze_default_highscores()
    with open("highscore.txt", "wb") as d:
        pickle.dump(highscore_liste, d)
        tk.messagebox.showinfo("Highscores", "Highscore-Liste neu angelegt")

def start(event):
    threading.Thread(target=spiel).start()

# Initialisierung:
laufband=[]
thread_flag=False # True = Nebenthread läuft    
leben=3
punkte=0
level=1
erste_farbe="#D6D7D6"
zweite_farbe="#969696"
dritte_farbe="#3B3838"
relief_effekt="groove"
relief_tiefe=2

# Toplevel Widget (Main Window):
main=tk.Tk()
main.protocol('WM_DELETE_WINDOW', beenden)
main.wm_title("Zahlenlaufband")
main.resizable(width=0, height=0)
main.configure(background=erste_farbe)

# Menü:
menubar = tk.Menu(main)
menu_datei = tk.Menu(menubar)
menu_datei.config(tearoff=0)
menu_datei.add_command(label="Zeige Highscores", command=zeige_highscores)
menu_datei.add_command(label="Setze Highscores zurück",
                       command=resette_highscores)
menu_datei.add_command(label="Beenden", command=beenden)
menubar.add_cascade(label="Datei", menu=menu_datei)
menu_relief = tk.Menu(menubar)
menu_relief.config(tearoff=0)
menu_relief.add_command(label="Flat", command=lambda: relief("flat"))
menu_relief.add_command(label="Raised", command=lambda: relief("raised"))
menu_relief.add_command(label="Sunken", command=lambda: relief("sunken"))
menu_relief.add_command(label="Groove", command=lambda: relief("groove"))
menu_relief.add_command(label="Ridge", command=lambda: relief("ridge"))
menubar.add_cascade(label="Relief-Stil", menu=menu_relief)
menu_relief_3d = tk.Menu(menubar)
menu_relief_3d.config(tearoff=0)
menu_relief_3d.add_command(label="Relief-Tiefe 1",command=lambda: relief_3d(1))
menu_relief_3d.add_command(label="Relief-Tiefe 2",command=lambda: relief_3d(2))
menu_relief_3d.add_command(label="Relief-Tiefe 3",command=lambda: relief_3d(3))
menu_relief_3d.add_command(label="Relief-Tiefe 4",command=lambda: relief_3d(4))
menu_relief_3d.add_command(label="Relief-Tiefe 5",command=lambda: relief_3d(5))
menu_relief_3d.add_command(label="Relief-Tiefe 6",command=lambda: relief_3d(6))
menubar.add_cascade(label="Relief-Tiefe", menu=menu_relief_3d)
menu_hilfe = tk.Menu(menubar)
menu_hilfe.config(tearoff=0)
menu_hilfe.add_command(label="Spielregeln", command=msg_hilfe)
menu_hilfe.add_command(label="Über das Spiel", command=msg_about)
menubar.add_cascade(label="Hilfe", menu=menu_hilfe)
main.config(menu=menubar)

# Row 0:
label_oben_links=tk.Label(main, text=4, width=3, fg="White", relief=relief_effekt,
    borderwidth=relief_tiefe, bg=zweite_farbe, font=("Arial 20"))
label_oben_links.grid(row=0, column=0)
label_punkte=tk.Label(main, bg=erste_farbe, font=("Arial 20"))
label_punkte.grid(row=0, column=1, pady=7)
label_oben_rechts=tk.Label(main, text=4, width=3, fg="White", relief=relief_effekt,
    borderwidth=relief_tiefe, bg=zweite_farbe, font=("Arial 20"))
label_oben_rechts.grid(row=0, column=2)

# Row 1:
label_mitte_links=tk.Label(main, text=5, width=3, relief=relief_effekt,
    borderwidth=relief_tiefe, fg="White", bg=dritte_farbe,
    font=("Arial 30 bold"))
label_mitte_links.grid(row=1, column=0, padx=7)
label_laufband=tk.Label(main, width=13, text="Drücke <Enter>", fg="White",
    bg="Black", font=("Arial 22"), relief=relief_effekt,
    borderwidth=relief_tiefe)
label_laufband.grid(row=1, column=1)
label_mitte_rechts=tk.Label(main, text=5, width=3, relief=relief_effekt,
    borderwidth=relief_tiefe, fg="White", bg=dritte_farbe,
    font=("Arial 30 bold"))
label_mitte_rechts.grid(row=1, column=2, padx=7)

#Row 2:
label_unten_links=tk.Label(main, text=6, width=3, relief=relief_effekt,
    borderwidth=relief_tiefe, fg="White", bg=zweite_farbe, font=("Arial 20"))
label_unten_links.grid(row=2, column=0)
label_leben=tk.Label(main, bg=erste_farbe, font=("Arial 20"))
label_leben.grid(row=2, column=1, pady=7)
label_unten_rechts=tk.Label(main, text=6, width=3, relief=relief_effekt,
    borderwidth=relief_tiefe, fg="White", bg=zweite_farbe, font=("Arial 20"))
label_unten_rechts.grid(row=2, column=2)

# Row 3 (Buttons):
rahmen=tk.Frame(main, bg=erste_farbe)
rahmen.grid(row=3, column=1)
tk.Button(rahmen, text="Abbruch", command=abbrechen).grid(row=0, column=0,
                                                       padx=7, pady=7)
tk.Button(rahmen, text="Ende", command=beenden).grid(row=0, column=1,
                                                  padx=7, pady=7)

# Row 4 (Info-Label):
label_info=tk.Label(main, text="Benutze <Enter>, <Pfeile> und <Leer> zum Spielen",
                 bg=erste_farbe)
label_info.grid(row=4, column=0, columnspan=3)

# Key Bindings:
main.bind("<KeyPress-Up>", reihe_hoch)
main.bind("<KeyPress-Down>", reihe_runter)
main.bind("w", reihe_hoch)
main.bind("s", reihe_runter)
main.bind("<space>", treffer)
main.bind("<Control_L>", treffer)
main.bind("<Control_R>", treffer)
main.bind("<Return>", start)
main.bind("<Tab>", tk.DISABLED)
main.bind("<Escape>", abbrechen)

main.mainloop()
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

Nabend allerseits,

ich hab mich inzwischen wie angedroht :-) and die OOP-Version gemacht und auch schon 2 Klassen aufgesetzt. Jetzt habe ich aber mal eine prinzipielle Frage, die auch nicht unbedingt direkt was mit OOP zu tun hat, sondern mit dem Vermeiden von globalen Variablen. Ich hatte ja in meinem bisherigen Code einige globale Variablen, die ihren Zweck wunderbar erfüllt hatten. Jetzt will ich das ganze in Klassen kapseln. Nur weiß ich nicht, wie man den Datenaustausch am geschicktesten organisiert. Ein Beispiel aus meinem Spiel:

Ich habe die Klasse "Doppelwalze". Darin enthalten sind die sechs Labels rechts und links, wo man die Ziffer mit den Pfeiltasten hoch/runter auswählen muss, so dass in der Mitte die "richtige" Ziffer ist. Auch sind in dieser Klasse zwei Methoden, "up" und "down", die dafür sorgen, dass die Ziffern in der Doppelwalze sich ändern.

Als zweite Klasse habe ich "Laufband". Hier laufen in einem weiteren Label (in der Mitte des Fensters) Zahlen von rechts nach links. Diese Zahlen habe ich datentyp-technisch mit einer Liste realisiert. Die Methode "start" (ausgelöst durch die Return-Taste) erzeugt einen Thread und ruft darin die Methode "spiel" auf, welche das eigentliche Spiel, also das Laufband startet. So habe ich es in meiner "alten" nicht OOP-Version auch schon gemacht. Ich hatte viel im Netz recherchiert und rumexperimentiert, u.a. mit der after-Methode. Das Problem war immer, dass das Laufband bzw. die damit einhergehende sleep-Funktion die Benutzung der Walze zum "Laggen" brachte. Einzig ein zweiter Thread hat dieses Problem gelöst, und zwar sehr wirkungsvoll. Ich weiß nicht, ob dies überhaupt die empfohlene Vorgehensweise ist, aber das ist ein anderes Thema. Hier geht es ja um Datenaustausch, ohne global zu verwenden.

Zurück zum eigentlichen Problem: Ich bin jetzt gedanklich wieder in der Klasse "Doppelwalze" und will die Methode "treffer", ausgelöst von der Leertaste, implementieren. Immer, wenn die Ziffer im mittleren Label mit der ganz linken Ziffer vom Laufband aus der anderen Klasse übereinstimmt, soll die ganz linke Ziffer vom Laufband gelöscht werden. Mein erster Gedanke, ich mache die Liste "laufband" global, so wie ich es in der alten Version gemacht habe. Aber das will ich ja gerade vermeiden.

Und an dieser Stelle benötige ich Eure Hilfe. Konkrete Lösungen, aber auch Denkansätze oder Links zu Tutorials - alles hochwillkommen!

Hier der bisherige Code. Bitte diese if-elif Konstruktionen erst Mal ignorieren. Mir ist klar, dass im Moment noch das selbe passiert und daher die Fallunterscheidung (noch) sinnlos ist. In der alten Version hatte ich ja, dass wenn die Walze am Anschlag ist, die jeweils äußeren Labels "unsichtbar" werden, das soll hier auch wieder rein. In der alten Version hatte ich das damit ereicht, dass ich von den betreffenden Labels die Schrift- und Hintergrundfarbe auf die selbe Farbe wie der Rahmen gesetzt hatte, und den Relief-Effekt auf "flat". So ist das Label unsichtbar :-) Gibt es vielleicht eine einfachere Lösung?

Code: Alles auswählen

import tkinter as tk
import random
import time
import threading

main=tk.Tk()
main.wm_title("Zahlenlaufband")
main.resizable(width=0, height=0)

class Doppelwalze:
    ll_1=tk.Label(main, text=4, font=("Arial 20"), width=3) #LabelLinks
    ll_2=tk.Label(main, text=5, font=("Arial 30"), width=3)
    ll_3=tk.Label(main, text=6, font=("Arial 20"), width=3)
    lr_1=tk.Label(main, text=4, font=("Arial 20"), width=3) #LabelRechts
    lr_2=tk.Label(main, text=5, font=("Arial 30"), width=3)
    lr_3=tk.Label(main, text=6, font=("Arial 20"), width=3)
    def __init__(self, row, column):
        self.ll_1.grid(row=row, column=column)
        self.ll_2.grid(row=row+1, column=column)
        self.ll_3.grid(row=row+2, column=column)
        self.lr_1.grid(row=row, column=column+2)
        self.lr_2.grid(row=row+1, column=column+2)
        self.lr_3.grid(row=row+2, column=column+2)
    def up(self, event):
        if self.ll_2.cget("text")==2:
            self.ll_1.config(text=self.ll_1.cget("text")-1)
            self.lr_1.config(text=self.lr_1.cget("text")-1)
            self.ll_2.config(text=self.ll_2.cget("text")-1)
            self.lr_2.config(text=self.lr_2.cget("text")-1)
            self.ll_3.config(text=self.ll_3.cget("text")-1)
            self.lr_3.config(text=self.lr_3.cget("text")-1)
        elif self.ll_2.cget("text")>1:
            self.ll_1.config(text=self.ll_1.cget("text")-1)
            self.lr_1.config(text=self.lr_1.cget("text")-1)
            self.ll_2.config(text=self.ll_2.cget("text")-1)
            self.lr_2.config(text=self.lr_2.cget("text")-1)
            self.ll_3.config(text=self.ll_3.cget("text")-1)
            self.lr_3.config(text=self.lr_3.cget("text")-1)
    def down(self, event):
        if self.ll_2.cget("text")==8:
            self.ll_1.config(text=self.ll_1.cget("text")+1)
            self.lr_1.config(text=self.lr_1.cget("text")+1)
            self.ll_2.config(text=self.ll_2.cget("text")+1)
            self.lr_2.config(text=self.lr_2.cget("text")+1)
            self.ll_3.config(text=self.ll_3.cget("text")+1)
            self.lr_3.config(text=self.lr_3.cget("text")+1)
        elif self.ll_2.cget("text")<9:
            self.ll_1.config(text=self.ll_1.cget("text")+1)
            self.lr_1.config(text=self.lr_1.cget("text")+1)
            self.ll_2.config(text=self.ll_2.cget("text")+1)
            self.lr_2.config(text=self.lr_2.cget("text")+1)
            self.ll_3.config(text=self.ll_3.cget("text")+1)
            self.lr_3.config(text=self.lr_3.cget("text")+1)

class Laufband:
    laufband=[]
    lb=tk.Label(main, width=13, text="Drücke <Enter>", font=("Arial 22"))
    def __init__(self, row, column):
        self.lb.grid(row=row, column=column)
    def spiel(self):
        sleep_counter=1.3
        self.lb.config(text="Los geht's ...   ", anchor="e")
        while (len(self.laufband)<10):
            for i in range(10):
                if (len(self.laufband)<10):
                    time.sleep(sleep_counter)
                    self.laufband.append(random.randint(1, 9))
                    self.lb.config(text=self.laufband, anchor="e")
        self.lb.config(text="GAME OVER", anchor="center")
        self.laufband.clear()
    def start(self, event):
        threading.Thread(target=self.spiel).start()

doppelwalze=Doppelwalze(0, 0)
laufband=Laufband(1, 1)

main.bind("<KeyPress-Up>", doppelwalze.up)
main.bind("<KeyPress-Down>", doppelwalze.down)
main.bind("<Return>", laufband.start)

main.mainloop()
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

Da fällt mir ein, ich hab in der Ausbildung mal was von Setter- und Getter-Methoden gehört. Ich glaube, das ist der richtige Ansatz hier :P
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

Ok, ich hab jetzt gemerkt - wohl wieder zu kompliziert gedacht - dass man in Python ja auf Variablen eines Objektes ganz einfach zugreifen kann, lesend und schreibend, da Variablen, anders als in Java, nicht privat sind.

Jetzt hab ich mir gedacht, ich übergebe der Methode "treffer" das Objekt "laufband" und greife somit auf die darin enthaltene Liste (laufband) zu, die in der alten Version global war. Ist es überhaupt sinnvoll, es so zu machen?

Falls ja, habe ich gleich das nächste Problem, was wohl ein tkinter Problem ist. Die Methode "treffer" hat ja jetzt einen Parameter, somit kann ich sie nicht mehr an eine Taste binden, da bind keine Parameter akzeptiert, sondern nur nackte Methoden mit dem Parameter "event". Das hat mich neulich schon immer genervt. An der Zeit mal zu fragen, ob jemand weiß, wie man das in den Griff kriegen kann? Ne Extra Funktion dafür zu definieren, kann's irgendwie nicht sein.
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

Habs selbst rausgefunden (schade, dass man Beiträge nach ner Weile nicht mehr editieren kann - sorry für die Posting-Flut):

Code: Alles auswählen

main.bind("<space>", lambda event: doppelwalze.treffer(event, laufband))
Die gute alte Lambda hats wieder gerichtet - damit hat sich die letzte Frage erledigt. Hmm, insgesamt ist das alles doch irgendwie Murks, was ich da mache, oder?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@way2slow: Du verwendest immer noch globale Variablen: main. Klassenattribute sind etwas anderes als Instanzattribute. Alle Deine auf Klassenebene definierten Attribute solltest Du nach __init__ verschieben. Alle Anweisungen auf Modulebene (außer Klassen-/Funktionsdefinitionen) sollten in eine Funktion, üblicherweise main genannt, verschoben werden. Dann kann es Dir auch nicht mehr passieren, dass Du aus Versehen Dein Tk-Fenster als globale Variable verwendest. Bei GUIs darf aus einem anderen Thread nichts verändert werden. Dein Laufband-Spiel solltest Du also mit after implementieren und nicht mit Thread.
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

hi sirius, danke.

ich hatte noch keine zeit weiterzumachen. aber das mit der after methode kann ich jetzt schon sagen: das klappt nicht, hatte ich schon probiert. das problem ist, dass das ganze programm lahmgelegt ist, so dass die walze nicht sofort reagiert. steht auch so in der doku zu after:

"The command sleeps for ms milliseconds and then returns. While the command is sleeping the application does not respond to events."

https://www.tcl.tk/man/tcl8.5/TclCmd/after.htm

ist das selbe wie bei sleep. in einem eigenen thread hingegen kann sleep oder after laufen, ohne die walze zu blokieren.
BlackJack

@way2slow: Doch das funktioniert mit `after()`. Du sollst das nicht als `sleep()`-Ersatz benutzen, sondern für die zeitverzögerte Ausführung, also *nicht* die erste Variante aus der Dokumentation sondern die Zweite!
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

Danke für den Tipp. Mit after hat es tatsächlich geklappt. Ich hab eine Rekursion mit if-Abfrage benutzt, die quasi wie eine Schleife funktioniert. Hoffe, so hat es der Erfinder gedacht.

Aber jetzt ne ganz andere Frage: Ich habe in meinem kleinen Spiel eine Liste (laufband.laufbandliste), die am Anfang leer ist, dann mit Zufallszahlen erweitert wird, und wo gelegentlich Elemente mit pop entfernt werden. In Zeile 62 in der Funktion check_treffer prüfe ich, ob die Liste nicht leer ist (if laufband.laufbandliste:) und führe in der nächsten Zeile einen Vergleich durch. Trotzdem wirft der Interpreter den Fehler "IndexError: list index out of range", wenn man besonders schnell spielt: Pfeil nach oben/unten gedrückt halten sollte zum Nachvollziehen genügen, wenn die Tastaturverzögerung hinreichend kurz eingestellt ist.

Also, zum einen begreife ich nicht genau, wieso die Stelle mit dem Vergleich überhaupt erreicht wird, wenn die Liste leer ist. Zum anderen würde mich grundsätzlich interessieren, wie man mit sowas umgeht? Der Index-Fehler verhindert ja nicht, dass das Spiel so läuft, wie es soll. Einfach so lassen? Oder mit try behandeln? Vermutlich ist es am saubersten, wenn man Indexfehler von vorneherein gar nicht erst zulässt? Nur in diesem Fall: Wie?

Code: Alles auswählen

import tkinter as tk
import random
import time
import pickle
import os
import collections

main=tk.Tk()
main.title("Zahlenlaufband")
main.resizable(width=0, height=0)

class Walze:
    walzenliste=collections.deque([1, 2, 3, 4, 5, 6, 7, 8, 9])
    ll_1=tk.Label(main, text=walzenliste[3], font=("Arial 20"), width=3,
                  relief="groove", borderwidth=2) #LabelLinks
    ll_2=tk.Label(main, text=walzenliste[4], font=("Arial 30"), width=3,
                  relief="groove", borderwidth=2)
    ll_3=tk.Label(main, text=walzenliste[5], font=("Arial 20"), width=3,
                  relief="groove", borderwidth=2)
    def __init__(self, row, column):
        self.ll_1.grid(row=row, column=column)
        self.ll_2.grid(row=row+1, column=column)
        self.ll_3.grid(row=row+2, column=column)
    def update_walze(self):
        self.ll_1.config(text=self.walzenliste[3])
        self.ll_2.config(text=self.walzenliste[4])
        self.ll_3.config(text=self.walzenliste[5])
    def up(self, event):
        self.walzenliste.rotate(1)
        check_treffer(self.walzenliste, laufband.laufbandliste)
        self.update_walze()
    def down(self, event):
        self.walzenliste.rotate(-1)
        check_treffer(self.walzenliste, laufband.laufbandliste)
        self.update_walze()

class Laufband:
    laufbandliste=[]
    lb=tk.Label(main, width=13, text="Drücke <Enter>", font=("Arial 22"),
                relief="groove", borderwidth=2)
    def __init__(self, row, column):
        self.lb.grid(row=row, column=column)
    def ermittle_zufallszahl(self):
        zufallszahl=random.randint(1, 9)
        while zufallszahl==walze.walzenliste[4]:
            zufallszahl=random.randint(1, 9)
        return zufallszahl
    def spiel(self, event=None):
        self.lb.config(anchor="e")
        if len(self.laufbandliste)<9:
            self.laufbandliste.append(self.ermittle_zufallszahl())
            self.lb.config(text=self.laufbandliste)
            self.lb.after(1000, self.spiel)
        else:
            self.lb.config(text="GAME OVER", anchor="center")
            self.laufbandliste.clear()
    def set_treffer(self):
        self.laufbandliste.pop(0)
        self.lb.config(text=self.laufbandliste)

def check_treffer(walzenliste, laufbandliste):
    if laufband.laufbandliste:
        while walzenliste[4]==laufbandliste[0]:
            print("TREFFER")
            laufband.set_treffer()
    else: print("xxxxxxx")

walze=Walze(0, 0)
laufband=Laufband(1, 1)

main.bind("<KeyPress-Up>", walze.up)
main.bind("<KeyPress-Down>", walze.down)
main.bind("<Return>", laufband.spiel)

main.mainloop()
BlackJack

@way2slow: Du prüfst zwar *vor* der Schleife ob die Liste nicht leer ist, aber *in* der Schleife wird ``laufband.set_treffer()`` aufgerufen und darin wird ein Element entfernt. Falls das das letzte Element war, knallt's beim nächsten Test der Schleifenbedingung.

Da ist aber noch so einiges falsch. Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Du hast da das Hauptprogramm stehen, und dann auch noch die Klassendefinitionen mitten drin, und definierst Variablen auf die dann fröhlich aus Funktionen und Methoden zugegriffen wird. Das ist total unübersichtlich. Funktionen und Methoden sollten ausser Konstanten nichts verwenden was nicht als Argument übergeben wurde. Das Hauptprogramm gehört in eine Funktion. Die heisst üblicherweise `main()`. An der Stelle ist dann `main` als Name für das `Tk`-Exemplar etwas ungünstig.

`time`, `pickle`, und `os` werden importiert, aber nicht verwendet.

Du hast da einiges an Klassenattributen was dort nicht hingehört. Also *alles* um genau zu sein. Klassenattribute sind nur sehr selten überhaupt sinnvoll und dann fast immer als Konstanten.

Dein `Walzen`-Exemplar greift ”magisch” auf das `Laufband`-Exemplar zu und das `Laufband`-Exemplar auf das `Walzen`-Exemplar. Das lässt sich nicht wirklich sinnvoll auflösen, der Klassenentwurf ist also Murks. Das liesse sich beispielsweise durch eine Klasse lösen die nur die Spiellogik enthält und keinen GUI-Code. So ein Exemplar kann man dann beiden GUI-Klassen-Exemplaren übergeben.

Grunddatentypen haben in Namen nichts zu suchen. Insbesondere dann nicht wenn sie auch noch falsch sind weil es gar keine Liste ist, die an einen gebunden wird der mit `liste` endet.
way2slow
User
Beiträge: 19
Registriert: Sonntag 14. Februar 2016, 22:27
Wohnort: Hamburg

Hmm, ich habe 2 Listen (walzenliste und laufbandliste), die beides Listen sind. Verstehe daher nicht so recht, was du mit dem aller letzten Satz meinst.

Zum Index-Fehler: Jetzt hab ich verstanden, woher der Fehler kommt. Immerhin. Nur, wenn ich aus dem while in Zeile 63 ein if mache, dann räumt er nur eine Zahl ab, wenn mehrere Gleiche vorkommen. Muss ich mir mal Gedanke machen, wie man das am Geschicktesten löst.

Edit: Habe jetzt einfach am Ende der While-Schleife ein

if not laufband.laufbandliste: break

reingebastelt :lol:

Ansonsten: Mal wieder 1000 Dank für die schnelle Hilfe. Da hab ich ja wieder viel zu tun, bzw. erst mal zu verstehen :-)
BlackJack

@way2slow: `walzenliste` ist keine Liste sondern eine `collections.deque`. Datentypen ändern sich im Laufe der Entwicklung ab und zu weil man eine andere speziellere Datenstruktur verwendet, wie zum Beispiel in diesem Fall, und dann muss man entweder überall den Namen anpassen oder man hat falsche, irreführende Namen im Quelltext stehen. Das passiert besonders häufig bei Grunddatentypen, wo man beispielsweise mit einer Liste anfängt, dann aber irgendwann feststellt das man an der Stelle lieber einen Sequenzdatentyp mit anderen, spezielleren Eigenschaften hätte, beispielsweise eine `deque` oder ein selbst geschriebener Datentyp.
Antworten