Hangman-Spiel Programmierung

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Unkreatief
User
Beiträge: 2
Registriert: Mittwoch 22. März 2017, 01:00

Hallo!
Ich bin ein junger Python-Neuling und musste mich kürzlich der Aufgabe der Programmierung eines Hangman-Spiels widmen.. Was ich bis heute nicht geschafft habe. Finde bei bestem Willen einfach nicht die Ursache dafür, dass der Code nicht funktioniert, bzw. die Labels nicht aktualisiert werden und schon der erste Anfangsbuchstabe eines Worts zum Sieg führt. :/
Wäre sehr froh über Hilfe oder Verbesserungsvorschläge .. Hier der ( Link zum ) Code:
http://pastebin.com/tRnV7rLz

Ich bedanke mich schon mal ganz herzlich im Voraus!

Code: Alles auswählen

#Hangman
import random
import time
from tkinter import *

Wort = ""
Wort2 = []
Wortlis = []
Wortanzeige = []
Versuch = ""
Versuchte = []
Fehler = 0
inside = False
mark = 0
msgHilfe = 0
labelSieg = 0
labelNiederlage = 0
Niederlage = False
Sieg = False


#Funktion======================================================================================
def newRound():
    global Gewonnen, Verloren, Fehler, Wortanzeige, Wort, Wort2, labelNiederlage, labelSieg, Niederlage, Sieg
    Woerter = open('E:\Hangman-Projekt\Woerter.txt', 'r')
    Woerterliste = []
    Wort2 = []
    Versuchte = []
    Fehler = 0
    if Sieg == True:
        labelSieg.destroy()
        Sieg = False
    if Niederlage== True:
        labelNiederlage.destroy()
        Niederlage = False
    for line in Woerter:
        line = line.strip()
        Woerterliste.append(line)
    Wort = Woerterliste[random.randrange(0, len(Woerterliste))]
    for i in range(1, len(Wort)+1, 1):
        Wort2.append(Wort[i - 1])
    for i in range(1, len(Wort)+1, 1):
        Wortanzeige.append("_")
    Woerter.close()

def popupHilfe():
    global mark, msgHilfe  #Durch global ist der Zugriff auf die Variablen ermöglicht
    if mark==0:
        msgHilfe=Message(dialogFenster,text="Hangman:\nErraten sie das Gesuchte Wort, indem sie nach und Nach einzelne \nBuchstaben erraten.\nVersuchen Sie dies mit möglichst wenigen Versuchen, \nda ihnen bei jedem Fehler ein Versuch abgezogen wird.")
        msgHilfe.pack()             #Sorgt für eine "schöne" Plazierung des Popup
        mark=1
    else:
        msgHilfe.destroy()
        mark=0
def inspection():
    global Fehler, Wortanzeige, Versuchte, inside, Wort, Wort2, labelSieg, laberNiederlage, Sieg, Niederlage
    Versuch = eingabeEingabe.get()
    inside = False
    for i in range(1, len(Wort) + 1, 1):
        if Versuch.upper() == Wort[i - 1] or Versuch.lower() == Wort[i - 1]:
            del Wortanzeige[i - 1]
            Wortanzeige.insert(i - 1, Wort[i-1])
            inside = True
    if inside == False:
        Fehler = Fehler + 1
    Versuchte = Versuchte + [Versuch]
    if Wortanzeige == Wort2:                 #Ist die Runde beendet?
        labelSieg = Label(master=dialogFenster, bg='green', text="Sie haben Gewonnen!")
        labelSieg.place(x=400, y=740)
        Sieg = True
    if Fehler == 10:
        labelNiederlage = Label(master=dialogFenster, bg='red', text="Sie haben Verloren!")
        labelSieg.place(x=400, y=740)
        Niederlage = True
#Funktion======================================================================================
#Dialogfenster erstellen
dialogFenster = Tk()
dialogFenster.title('Hangman')
dialogFenster.geometry('600x780')

#Labels
labelFehler = Label(master=dialogFenster, text=("Fehler:", Fehler), fg='red', font=("Helevatica", 14))
labelFehler.place(x=10, y=10)

labelWortanzeige = Label(master=dialogFenster, text=("Gesucht:", Wortanzeige), fg='blue', bg='white')
labelWortanzeige.place(x=100, y=550, width=400, height=50)

labelVersuchte = Label(master=dialogFenster, text=("Versuchte:", Versuchte), fg='blue', bg='white')
labelVersuchte.place(x=100, y=630, width=400, height=50)

#Entrys
eingabeEingabe = Entry(master=dialogFenster, bg='#d8d9e3')
eingabeEingabe.place(x=280, y=700, width=60, height=30)

#Buttons 
buttonHilfe = Button(master=dialogFenster, text="Hilfe", command=lambda: popupHilfe())      #mit lambda lassen sich beliebig vile anonyme kunktionen erstellen. Ohne wäre hier das popup dauerhaft angezeigt...
buttonHilfe.place(x=560, y=0, width=40, height=20)

buttonEingabe = Button(master=dialogFenster, text="Eingabe", bg='#d8d9e3', command=lambda: inspection())
buttonEingabe.place(x=280, y=740, width=60, height=30)

buttonnewRound = Button(master=dialogFenster, text='Neue Runde', command=lambda: newRound())
buttonnewRound.place(x=120, y=740, width=80, height=30)


#Dialogfenster aktivieren
dialogFenster.mainloop()
Zuletzt geändert von Anonymous am Mittwoch 22. März 2017, 01:24, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Unkreatief: *-Importe sind schlecht, weil sie unkontrolliert Namen in den aktuellen Namensraum reinladen. Üblicherweise importiert man tkinter über "import tkinter as tk" und referenziert alle Namen mit tk.xxx. Vergiss dass es global gibt. Wenn Du programmieren lernen willst/mußt, dann lerne, wie man ohne global auskommt. Bei 14 Variablen, die am Stück definiert werden, verliert sowieso jeder den Überblick. Bei GUI-Programmierung kommt man eigentlich nicht um Objektorientierung herum. Solltest Du das noch nicht gelernt haben, lass die graphische Umsetzung erst einmal weg und konzentriere Dich auf ein konsolenbasiertes Hangman. Schreibe Funktionen, die nur eine Aufgabe erfüllen. Eine Funktion zum Laden der Wörter, zum Auswählen eines Wortes, zum Erzeugen des Lückenwortes mit aktuell gefundenen Buchstaben, usw.
Beispiel zur Verdeutlichung: newRound öffnet eine Datei, zerstört irgendwelche Labels, liest dann aus der Datei, erzeugt dann den Lückenwort und schließt eine Datei. Drei Aufgaben in einer Funktion, wobei die erste Aufgabe auch noch quer über die ganze Funktion verteilt ist. Das ist unübersichtlich, schwer verständlich und das Finden von Fehlern kann dadurch fast unmöglich werden.

Daher: erste Aufgabe, mach Dir einen Plan. Wie läuft eine Spielrunde ab? Welche Aufgaben sind wann zu erledigen? Wie würdest Du die Aufgaben ein Funktionen einteilen? Was sind die Eingabe(n) und Ausgabe(n) jeder dieser Funktionen?
BlackJack

@Unkreatief: Noch ein paar zusätzliche Anmerkungen:

`time` wird importiert aber nirgends verwendet.

Die Namen sind teilweise schlecht gewählt. Namen sollen dem Leser vermitteln was der Wert der daran gebunden wird, innerhalb des Programms bedeutet. `Wort2` tut das nicht. `inside`: Was? Worin? `mark`: Markierung von oder für was? Funktionen und Methoden werden üblicherweise nach der Tätigkeit benannt die sie ausführen. Also beispielsweise `start_new_round()` statt `newRound()` oder `inspect()` statt `inspection()`, wobei letzteres wieder ziemlich generisch ist und durch einen aussagekräftigeren Namen ersetzt werden könnte.

Die konventionelle Namensschreibweise ist `klein_mit_unterstrichen` für alles ausser Konstanten und Klassennamen. Siehe auch den Style Guide for Python Code.

Neben Nummerierungen (`Wort2`) sollten in Namen auch keine Grunddatentypen oder Abkürzungen davon (`Wortlis`) vorkommen. Bei Nummerierungen will man entweder keine einzelnen Namen sondern eine Datenstruktur, oder sich bessere, passendere Namen für die Einzelwerte ausdenken. Abkürzungen sollte man vermeiden weil es den Leser unnötig zum rätselraten zwingt und die Gefahr birgt, das der falsch rät und das Programm dadurch schwerer verständlich ist. Und Grunddatentypen im Namen haben das Problem, das man den Datentyp dann nicht mehr so leicht ändern kann ohne auch überall im Programm die betroffenen Namen zu ändern oder man hat Namen die nicht mit dem tatsächlichen Datentyp übereinstimmen und damit Verwirrung stiften.

Nun wollte ich gerade versuchen einen besseren Namen für `Wortlis` vorzuschlagen und musste feststellen, dass dieser Namen überhaupt gar nicht verwendet wird im Programm. Das ist eine Folge von diesen vielen Variablen auf Modulebene und den ganzen ``global``-Anweisungen: Man muss das gesamte Programm im Kopf haben und auf einmal erfassen und verstehen, und das geht halt nicht und führt zu Variablen die gar nicht verwendet werden, weil man den Überblick verloren hat und zu Fehlern die sich unnötig schwer fassen/isolieren lassen. Auf der anderen Seite habe ich mindestens eine lokale Variable gefunden die ``global`` sein müsste damit das so funktioniert. Globaler Zustand und ``global`` sind keine Lösung sondern ein Problem. Zudem hast Du ein paar Sachen die global sind, es aber gar nicht sein müssten.

Am Anfang werden einige Namen an eine 0 gebunden die im Laufe des Programms dann aber an ganz andere Datentypen als Zahlen gebunden werden. Das ist verwirrend. Ein Namen sollte immer nur an einen (Duck-)Typ gebunden werden. Als ”Platzhalter” für ”nichts” oder „noch nicht bekannt“ gibt es `None`.

Kommentare sollten dem Leser einen Mehrwert gegenüber dem Quelltext liefern. Vor einer Funktiondefinition zu kommentieren das da eine Funktion definiert wird, macht keinen Sinn. Vor dem Hauptprogramm auf Modulebene den selben Kommentar zu schreiben obwohl dort *keine* Funktion definiert wird, noch weniger. Auch Kommentare die die Sprache erklären haben in Programmen eigentlich nichts zu suchen. Wenn jemand nicht weiss was ``global`` macht, kann er das in der Python-Dokumentation nachlesen. Faustregel: Kommentieren warum der Code das so macht was er macht, denn was er macht steht da ja schon im Code. Und dann auch nur wenn das warum nicht offensichtlich ist oder etwas potentiell überraschendes passiert.

Der Kommentar zum ``lambda`` ist falsch. Keiner der ``lambda``-Ausdrücke in dem Programm wäre nötig, man könnte jedes mal einfach die Funktion direkt übergeben, statt deren Aufruf noch mal in eine anonyme Funktion zu verpacken.

Du hast da einige ``for``-Schleifen über einen Index `i` die von 1 bis Länge von etwas +1 gehen, wo in der Schleife niemals `i` direkt benutzt wird sondern immer nur ``i - 1``. Es wäre weniger umständlich und fehleranfällig wenn man statt grundsätzlich 1 abzuziehen, einfach gleich über um 1 kleinere Werte iterieren würde. Bei Computern ist es üblich bei 0 mit dem Zählen anzufangen. Noch besser: Versuche Indexvariablen zu vermeiden. Das ist in Python nicht nur möglich, sondern auch üblich und ”pythonisch”. Man kann direkt über die Werte einer Sequenz wie beispielsweise Listen iterieren. Und man kann auch über die Werte von zwei Sequenzen ”parallel” iterieren mit Hilfe der `zip()`-Funktion. Zudem ist es üblich neue Listen zu erstellen statt bestehende zu verändern.

Wobei Du das aktualisieren der `Wortanzeige` auch sehr umständlich betreibst. Du ersetzt Zeichen in dem Du die erst aus der Liste löschst um dann den neuen Wert einzufügen, statt einfach den Wert mit einer einfachen Zuweisung an den Index zu ersetzen.

Du solltest auch versuchen Flags zu minimieren, und zu schauen ob man die nicht aus den bereits vorhandenen Daten ablesen kann. Ob ein Buchstabe im Wort enthalten ist muss man beispielsweise nicht in der Schleife als Flag mitführen sondern kann das einfach mit ``character in word`` testen. `mark` kann man einfach loswerden in dem man schaut ob an den Namen für das `Message`-Objekt `None` gebunden ist und den Namen dann entweder an `None` oder das `Message`-Objekt bindet. Das es zwei Flags gibt die `Sieg` und `Niederlage` heissen, also entgegengesetzte Zustände kodieren, ist mindestens fragwürdig, weil ja immer nur einer davon wahr sein darf, aber nie beide. Das könnte man auch in einem Wert kodieren der neben `True` und `False` noch `None` annehmen könnte.

`place()` ist keine gute Idee. Man sollte das Layout Tk überlassen, denn man weiss ja nicht was der Benutzer für ein Display und Einstellungen hat, also ob das mit den absoluten Positionen so hin haut, oder gar unbenutzbar ist.
Unkreatief
User
Beiträge: 2
Registriert: Mittwoch 22. März 2017, 01:00

Hallo! Ich habe versucht das ganze ein wenig zu verändern, jedoch sitze ich nun wieder auf einem neuen Fehler .. womit könnte ich denn global ersetzen?

Aktuell zeigt er im GUI dann zwar immer an wie lang das Wort ist, bzw. wie viele Stellen es hat, aber zeigt eingegebene Buchstaben nicht an bzw übernimmt sie nicht. Pastebin: http://pastebin.com/6r0scU6y

Code: Alles auswählen

#Hangman
import random
from tkinter import *

Wort = ""
wort_als_liste = []
Wortanzeige = []
Versuch = ""
Versuchte = []
Fehler = 0
mark_popup_hilfe = 0
msgHilfe = None
Gewonnen= None


#Funktion======================================================================================
def suche_neues_wort():
    global Wort
    Woerterliste = []
    Woerter = open('G:\Hangman-Projekt\Woerter.txt', 'r')
    for line in Woerter:
        line = line.strip()
        Woerterliste.append(line)
    Wort = Woerterliste[random.randrange(0, len(Woerterliste))]
    Woerter.close()

def zerstoere_alte_daten():
    global Gewonnen
    Versuchte = []
    Fehler = 0
    if Gewonnen == True:
        labelSieg.destroy()
        Gewonnen = None
    if Gewonnen == False:
        labelNiederlage.destroy()
        Gewonnen = None

def erstelle_wortanzeige():
    global wort_als_liste, Wortanzeige
    for i in range(0, len(Wort), 1):
        wort_als_liste.append(Wort[i])
    for i in range(0, len(Wort), 1):
        Wortanzeige.append("_")

def aktualisiere_anzeigen():
    labelFehler.configure(text=("Fehler:", Fehler))
    labelWortanzeige.configure(text=("Gesuchte:", Wortanzeige))
    labelVersuchte.configure(text=("Versuchte:",Versuchte))
    eingabeEingabe.delete(0,END)
        
def start_neue_runde():
    zerstoere_alte_daten()
    suche_neues_wort()
    erstelle_wortanzeige()
    aktualisiere_anzeigen()

def popup_hilfe():
    global mark_popup_hilfe, msgHilfe
    if mark_popup_hilfe==0:
        msgHilfe=Message(dialogFenster,text="Hangman:\nErraten sie das Gesuchte Wort, indem sie nach und Nach einzelne \nBuchstaben erraten.\nVersuchen Sie dies mit möglichst wenigen Versuchen, \nda ihnen bei jedem Fehler ein Versuch abgezogen wird.")
        msgHilfe.pack()
        mark_popup_hilfe=1
    else:
        msgHilfe.destroy()
        mark_popup_hilfe=0

def ist_zeichen_gueltig():
    global Fehler, Wortanzeige, Versuchte, Wort, wort_als_liste
    Versuch = eingabeEingabe.get()
    inside_wort = False
    for i in range(0, len(Wort), 1):
        if Versuch.upper() == Wort[i] or Versuch.lower() == Wort[i]:
            del Wortanzeige[i]
            Wortanzeige.insert(i, Wort[i])
            inside_wort = True
    if inside_wort == False:
        Fehler = Fehler + 1
    Versuchte = Versuchte + [Versuch]

def ist_runde_beendet():
    if Wortanzeige == wort_als_liste:
        labelSieg = Label(master=dialogFenster, bg='green', text="Sie haben Gewonnen!")
        labelSieg.place(x=400, y=740)
        Gewonnen = True
    if Fehler == 10:
        labelNiederlage = Label(master=dialogFenster, bg='red', text="Sie haben Verloren!")
        labelSieg.place(x=400, y=740)
        Gewonnen = False

def button_eingabe():
    ist_zeichen_gueltig
    ist_runde_beendet()
    aktualisiere_anzeigen()
#Funktion======================================================================================
#Dialogfenster erstellen
dialogFenster = Tk()
dialogFenster.title('Hangman')
dialogFenster.geometry('600x780')

suche_neues_wort()
erstelle_wortanzeige()

#Labels
labelFehler = Label(master=dialogFenster, text=("Fehler:", Fehler), fg='red', font=("Helevatica", 14))
labelFehler.place(x=10, y=10)

labelWortanzeige = Label(master=dialogFenster, text=("Gesucht:", Wortanzeige), fg='blue', bg='white')
labelWortanzeige.place(x=100, y=550, width=400, height=50)

labelVersuchte = Label(master=dialogFenster, text=("Versuchte:", Versuchte), fg='blue', bg='white')
labelVersuchte.place(x=100, y=630, width=400, height=50)

#Entrys
eingabeEingabe = Entry(master=dialogFenster, bg='#d8d9e3')
eingabeEingabe.place(x=280, y=700, width=60, height=30)

#Buttons 
buttonHilfe = Button(master=dialogFenster, text="Hilfe", command=lambda: popup_hilfe())
buttonHilfe.place(x=560, y=0, width=40, height=20)

buttonEingabe = Button(master=dialogFenster, text="Eingabe", bg='#d8d9e3', command=button_eingabe())
buttonEingabe.place(x=280, y=740, width=60, height=30)

buttonstart_neue_runde = Button(master=dialogFenster, text='Neue Runde', command=start_neue_runde())
buttonstart_neue_runde.place(x=120, y=740, width=80, height=30)


#Dialogfenster aktivieren
dialogFenster.mainloop()
Zuletzt geändert von Anonymous am Mittwoch 22. März 2017, 16:54, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Schreibe Funktionen! Also Funktionen, die auch Rückgabewerte haben.

Code: Alles auswählen

def suche_neues_wort():
    woerter = []
    with open('G:\Hangman-Projekt\Woerter.txt', 'r') as lines:
        for line in lines:
            woerter.append(line.strip())
    return random.choice(woerter)
Dann Teste Deine Funktionen ohne GUI.
Antworten