Hangman (Wörterraten)

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
maik
User
Beiträge: 6
Registriert: Dienstag 2. Mai 2017, 11:27

Moin Moin,

ich habe ein kleines Spiel Programmiert mit Grafischer Benutzeroberfläche in dem man Wörter erraten muss. Möglich ist das ganze in Deutscher und in Englischer Sprache, Es ist ein kleines Punktesystem dabei und am Ende wird die erreichte Gesamtpunktzahl angezeigt. Als nächsten Schritt möchte ich eine Highscore-Liste einfügen und das ganze über eine Webseite nutzbar machen. Außerdem sollen Fehlerhafte Wörter gemeldet werden können, evtl. per e-mail Benachrichtigung an mich über den bereits eingefügten Button auf der rechten Seite.
Wichtig: Es müssen die zwei Dateien (wortliste_de.txt und wortliste_en.txt) mit den Wortlisten im gleichen Verzeichnis liegen.
Wortliste DE: https://www.dropbox.com/s/23iccovudy2z3 ... e.txt?dl=0
Wortliste EN: https://www.dropbox.com/s/b6tf2yd1qpcc2 ... n.txt?dl=0

Code: Alles auswählen

from tkinter import *
from random import *


class Hangman:
    """Spiel"""
    def __init__(self):
        # Fenster und Rahmen
        self.fenster = Tk()
        self.fenster.title('Wörterraten')
        self.font = 'Helvetica'
        self.entry = StringVar()
        self.rahmen_oben = Frame(master=self.fenster, relief=FLAT)
        self.rahmen_mitte = Frame(master=self.fenster, relief=FLAT, width=110)
        self.rahmen_unten = Frame(master=self.fenster, relief=FLAT)
        self.rahmen_unten_l = Frame(master=self.rahmen_unten, relief=FLAT, width=10)
        self.rahmen_unten_r = Frame(master=self.rahmen_unten, relief=FLAT, width=20)
        # Benutzeroberfläche
        self.titel = Label(master=self.rahmen_oben, text='Wörterraten', font=(self.font, 24), width=30)
        self.punkte_label = Label(master=self.rahmen_oben, text='Punkte: ', font=(self.font, 16))
        self.wort_label = Label(master=self.rahmen_oben, text='Wort: ', font=(self.font, 16))
        self.wortzahl = Wortzahl(self.rahmen_oben)
        self.max_woerter = Label(master=self.rahmen_oben, text='/5', font=(self.font, 16), bg='LightCyan2')
        self.tip = Label(master=self.rahmen_oben, text='Treffer: +1 Pkt.\tFehlversuch: -1 Pkt.\tWort erraten: +5 Pkt.',
                         font=(self.font, 10), fg='black')
        # Objekte
        self.sprache = Sprache(self.rahmen_unten_l)
        self.punkte = Punkte(self.rahmen_oben, 20)
        self.ausgabe = Ausgabe(self.rahmen_mitte, self.sprache)
        self.fehler_melden = Fehler(self.rahmen_mitte, self.sprache, self.ausgabe)
        self.eingabe = Entry(master=self.rahmen_unten_r, textvariable=self.entry, font=(self.font, 24),
                             width=2, bg='white')
        self.ok = ButtonOK(self.rahmen_unten_r, self.eingabe, self.ausgabe, self.punkte, self.wortzahl)
        self.neu = ButtonNeu(self.rahmen_unten_r, self.punkte, self.sprache, self.ausgabe, self.wortzahl, self.ok)
        self.layout()
        self.fenster.mainloop()

    def layout(self):
        self.rahmen_oben.pack(side=TOP, padx=20)
        self.rahmen_mitte.pack(side=TOP, fill=X)
        self.rahmen_unten.pack(side=TOP, fill=X)
        self.rahmen_unten_l.pack(side=LEFT)
        self.rahmen_unten_r.pack(side=RIGHT)
        self.titel.pack(padx=20, pady=10, side=TOP)
        self.tip.pack(side=BOTTOM)
        self.punkte.label.pack(side=RIGHT)
        self.punkte_label.pack(side=RIGHT)
        self.wort_label.pack(side=LEFT)
        self.wortzahl.label.pack(side=LEFT)
        self.max_woerter.pack(side=LEFT)
        self.fehler_melden.button.pack(side=RIGHT, padx=20, anchor=E)
        self.ausgabe.label.pack(padx=10, pady=10, side=TOP)
        self.neu.button_neu.pack(side=LEFT, padx=20, pady=5)
        self.eingabe.pack(side=LEFT)
        self.ok.button.pack(side=RIGHT, padx=20, pady=5)


class Punkte:
    def __init__(self, master, p):
        self.punkte = IntVar()
        self.punkte.set(p)
        self.label = Label(master, textvariable=self.punkte, width=2, font=('Helvetica', 16), bg='LightCyan2')

    def aktualisiere(self, wert):
        self.punkte.set(self.punkte.get() + wert)


class Wortzahl:
    def __init__(self, master):
        self.wortzahl = IntVar()
        self.wortzahl.set(1)
        self.label = Label(master, textvariable=self.wortzahl, font=('Helvetica', 16), bg='LightCyan2')


class Ausgabe:
    def __init__(self, master, sprache):
        self.master = master
        self.sprache = sprache
        self.wort = self.sprache.wg.get_wort()
        self.ausgabe = len(self.wort) * '-'
        self.label = Label(self.master, text=self.ausgabe, width=int(len(self.wort)*1.5),
                           font=('Helvetica', 30), bg='white')

    def neues_wort(self):
        self.wort = self.sprache.wg.get_wort()
        self.ausgabe = len(self.wort) * '-'
        self.label.config(text=len(self.wort)*'-')
        self.label.config(width=int(len(self.wort)*1.5))


class Fehler:
    def __init__(self, master, sprache, ausgabe):
        self.master = master
        self.sprache = sprache
        self.ausgabe = ausgabe
        self.button = Button(master, text='Fehlerhaftes\nWort melden',
                             font=('Helvetica', 10), command=self.fehler_melden)

    def fehler_melden(self):
        wort = self.ausgabe.wort
        sprache = self.sprache.sprache.get()
        print('Gemeldetes Wort: ' + wort + ', Sprache: ' + sprache)


class Sprache:
    def __init__(self, master):
        self.master = master
        self.sprache = StringVar()
        self.font = 'Helvetica'
        self.de = Radiobutton(master, text='Deutsch', font=self.font, value='de',
                              variable=self.sprache)
        self.en = Radiobutton(master, text='Englisch', font=self.font, value='en',
                              variable=self.sprache)
        self.de.pack(padx=20)
        self.de.select()
        self.en.pack(padx=20, pady=10)
        self.wg = Wortgenerator(self.sprache)

    def get_datei(self):
        return self.sprache.get()

    def neuer_wg(self):
        self.wg = Wortgenerator(self.sprache)


class ButtonNeu:
    def __init__(self, master, punkte, sprache, ausgabe, wortzahl, ok):
        self.master = master
        self.punkte = punkte
        self.sprache = sprache
        self.ausgabe = ausgabe
        self.wz = wortzahl
        self.ok = ok
        self.button_neu = Button(master, text='Neu', font=('Helvetica', 12), command=self.neues_spiel)

    def neues_spiel(self):
        self.sprache.neuer_wg()
        self.punkte.punkte.set(20)
        self.wz.wortzahl.set(1)
        self.ausgabe.neues_wort()
        self.ok.button.config(command=self.ok.pruefe)


class ButtonOK:
    def __init__(self, master, eingabe, ausgabe, punkte, wortzahl):

        self.master = master
        self.eingabe = eingabe
        self.ausgabe = ausgabe
        self.punkte = punkte
        self.wz = wortzahl
        self.button = Button(master, text='OK', font=('Helvetica', 12), command=self.pruefe)

    def pruefe(self):
        z = self.eingabe.get().upper()
        ausgabeliste = list(self.ausgabe.ausgabe)
        self.eingabe.delete(0, 2)
        if z in self.ausgabe.wort and z not in ausgabeliste:
            # Ausgabe aktualisieren
            for b in range(len(self.ausgabe.wort)):
                if self.ausgabe.wort[b] == z:
                    ausgabeliste[b] = z
            self.ausgabe.ausgabe = ausgabeliste
            self.ausgabe.label.config(text=''.join(ausgabeliste))
            self.punkte.aktualisiere(1)
            # Wenn Wort vollständig, wird neues Wort erzeugt
            if '-' not in self.ausgabe.ausgabe:
                if self.wz.wortzahl.get() == 5:
                    self.ausgabe.label.config(text=str('Fertig! ' + str(self.punkte.punkte.get()) + ' Punkte'))
                    self.ausgabe.label.config(width=18)
                    self.button.config(command='')
                else:
                    self.button.after(1000, self.ausgabe.neues_wort)
                    self.punkte.aktualisiere(5)
                    self.wz.wortzahl.set(self.wz.wortzahl.get() + 1)
        else:
            self.punkte.aktualisiere(-1)


class Wortgenerator:
    def __init__(self, sprache):
        self.sprache = sprache.get()
        datei = 'wortliste_' + self.sprache + '.txt'
        self.wortliste = []
        f = open(datei, 'r')
        self.liste = f.readlines()
        f.close()
        for x in self.liste:
            w = list(x)
            del w[-1]
            wort = ''
            for buchstabe in w:
                wort += buchstabe
            self.wortliste.append(wort.upper())

    def get_wort(self):
        # Liefert aus Wortliste ein Zufallswort
        return choice(self.wortliste)

Hangman()
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@maik: *-Importe sind böse, weil man nicht kontrollieren kann, was da einem in den Namensraum geladen wird. Die __init__-Methode einer Klasse soll eine Instanz initialisieren. Hangman.__init__ macht viel mehr, aka, das mainloop gehört da nicht rein. Warum sind Erzeugen von Widgets und deren Platzierung in unterschiedlichen Methoden? Würde das .pack immer gleich beim Erzeugen stehen, könnte man sie viele Instanzattribute sparen.
Die Klassen Punkte, Wortzahl, Ausgabe, etc. sind eigentlich keine wirklichen Klassen. Das Erzeugen der Labels wäre besser in Hangman.__init__ aufgehoben und das Aktualisieren der Labels als Methoden in Hangman. Bei ButtonNeu sieht man schön, dass das eigentlich keine eigenständige Klasse ist, weil 6! Objekte aus dem übergeordneten Fenster als Argumente übergeben werden müssen. Wenn man über mehrere Objekte hinweg auf etwas zugreifen muß (self.ok.button.config) ist das auch ein Zeichen dafür, dass hier Aufgaben falsch getrennt wurden.

Bei Wortgenerator solltest Du Dir anschauen, welche Methoden Strings haben, sprache sollte gleich ein String sein, so dass es egal ist, woher dieser Wert kommt. Nutze .format statt Strings mit + zusammenzustückeln. Kommentare, was eine Funktion macht, schreibt man in Python als sogenannten Doc-String:

Code: Alles auswählen

class Wortgenerator:
    def __init__(self, sprache):
        self.sprache = sprache
        datei = 'wortliste_{}.txt'.format(sprache)
        with open(datei, 'r') as lines:
            self.wortliste = [line.strip() for line in lines]
 
    def get_wort(self):
        """ Liefert aus Wortliste ein Zufallswort """
        return choice(self.wortliste)
BlackJack

@maik: Die Code-Aufteilung ist falsch würde ich sagen. Ich sehe da keine Trennung zwischen Programmlogik und GUI und Klassen die entweder nicht richtig kapseln oder einfach Funktionen sein könnten. Im Grunde sind das meiste Methoden die jeweils in eine eigene Klasse verschoben wurden. Klassen die nur aus einer `__init__()` und *einer* weitereren Methode bestehen sind ein „code smell“. Wenn das ganze Programm nur aus solchen Klassen besteht, ist ziemlich sicher das etwas nicht stimmt.

Die Objekte wissen teilweise auch zu viel voneinander und greifen zu tief auf andere Objekte durch. Beispielsweise `self.sprache.wg.get_wort()` in `Ausgabe`. Das ist ein Objekt das sich um die Darstellung der Ausgabe kümmern sollte, und keine Sprache kennen sollte, und noch weniger wissen sollte das eine Sprache einen Wortgenerator hat.

Sternchen-Importe sind Böse™. Damit müllt man sich den Namensraum voll, man sieht nicht mehr so einfach wo Namen definiert werden, und es besteht die Gefahr von Namenskollisionen.
maik
User
Beiträge: 6
Registriert: Dienstag 2. Mai 2017, 11:27

Vielen Dank erst mal für die Hinweise. Ich bin noch recht neu in der Materie und werde eure Hinweise mal umsetzen und daraus lernen.
Antworten