Hangman Spiel, läuft nicht richtig (Tkinter)

Fragen zu Tkinter.
Antworten
luftblumexo
User
Beiträge: 1
Registriert: Sonntag 5. September 2021, 17:11

Hallo,
ich bin Studentin und muss das Python Modul belegen, hatte leider unrealistisch kurze Zeit mir alles selbst beizubringen bis zum Abschlussprojekt, wo wir jetzt plötzlich eine GUI erstellen sollen. Unser Projekt ist das Spiel Hangman. Ich habe Code aus dem Internet zusammengewürfelt (shame on me) habe aber jetzt Probleme beim Verstehen des Codes, der auch nicht richtig funktioniert. Mir werden keine Fehler angezeigt, aber beim Eingeben eines Buchstabens ändern sich das Galgenbild nicht und der Buchstabe wird auch nicht bei den Strichen angezeigt. Vielleicht kann ja jemand helfen:D

Code: Alles auswählen

import tkinter as tk
from tkinter import messagebox
import PIL.Image
from PIL import Image, ImageTk
import random

class MainPage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)


        # Button erstellen
        button_a = tk.Button(self, text="Anleitung", command=lambda: controller.show_frame(SecondPage))
        button_a.pack()
        button_b = tk.Button(self, text="Spiel starten", command=lambda: controller.show_frame(ThirdPage))
        button_b.pack()

        # Bild erstellen
        bild = tk.PhotoImage(file="Hangman.png")
        label1 = tk.Label(self, image=bild)
        label1.pack()

        # Referenz erstellen
        labelr = tk.Label(self, text=" Ein Spiel von: May-Britt Jacobs, Neele Baumgart und Alea Barbato")
        labelr.pack(side="bottom")

# Klasse für die Anleitung
class SecondPage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        labela = tk.Label(self, text=" ANLEITUNG", bg="blue", fg="white")
        labela.pack()
        label2 = tk.Label(self, text="Finde das gesuchte Wort! Dafür versuchst du die Buchstaben zu erraten. Aber vorsicht! Du hast nur eine begrenzte Anzahl an Versuchen!",
                         bg="black", fg="white")
        label2.pack()



        button_c= tk.Button(self, text="Zurück", command=lambda:controller.show_frame(MainPage))
        button_c.pack(side="bottom")

# Klasse für das Spiel
class ThirdPage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)


        button_c = tk.Button(self, text="Zurück", command=lambda: controller.show_frame(MainPage))
        button_c.grid(row=5)

        #  Wortliste
        wortliste = ["Umweltschutzorganisation", "Nahrungsmittel", "Haftpflichtversicherung",
                     "Finanzdienstleistungsunternehmen",
                     "Liebesabenteuer", "Toilettenpapier", "Tierschutzverein", "Fussballweltmeisterschaft",
                     "mutterseelenallein",
                     "Kopfsteinpflaster", "Meerjungfrau",
                     "Warenentgegenhame", "Mindesthaltbarkeitsdatum", "Globalisierung", "Reiseveranstalterin",
                     "Gymnasialdirektorin", "Druckventil", "Quizshow", "Vollmond", "Hupfdohle", "Hollywood", "Kopfkino",
                     "Gigolo", "Babypuppe", "Puderzucker", "Kuddelmuddel", "Yeti", "Recycling"]

        #  Bilder für die Stadien des Galgenmännchens
        img1 = PIL.Image.open("3.png")
        img2 = PIL.Image.open("4.png")
        img3 = PIL.Image.open("5.png")
        img4 = PIL.Image.open("6.png")
        img5 = PIL.Image.open("7.png")
        img6 = PIL.Image.open("8.png")
        img7 = PIL.Image.open("9.png")
        img8 = PIL.Image.open("10.png")
        bilder = [ImageTk.PhotoImage(image=img1), ImageTk.PhotoImage(image=img2), ImageTk.PhotoImage(image=img3), ImageTk.PhotoImage(image=img4),
                  ImageTk.PhotoImage(image=img5), ImageTk.PhotoImage(image=img6), ImageTk.PhotoImage(image=img7), ImageTk.PhotoImage(image=img8)]

        def spielen():
            global stellen_wort  #  Variable erstellen, die nicht nur innerhalb der Funktion funktioniert
            global versuche
            global leben
            leben = 8
            versuche = 0
            imgLabel.config(image=bilder[0])  #Iterieren über die Bilder, sodass sie sich auswechseln lassen
            zufall_wort = random.choice(wortliste)
            stellen_wort = " ".join(zufall_wort)
            lblWord.set(" ".join("_" * len(zufall_wort)))  #Striche für die Länge des Wortes

        def erraten(*event):
            buchstabe_zu_erraten = txtWord.get().upper()  #txtWord ist die Variable, die den eingegebenen Buchstaben enthält, hier in Großbuchstaben
            txtWord.set("")  #eingegebene Buchstaben lesbar machen
            global versuche
            if versuche < leben:
                txt = list(stellen_wort)
                print(txt)
                erraten = list(lblWord.get())
                if stellen_wort.count(buchstabe_zu_erraten) > 0:
                    for i in range (len(txt)):
                        if txt[i] == buchstabe_zu_erraten:
                            erraten[i] = buchstabe_zu_erraten
                        lblWord.set("".join(erraten))
                        if lblWord.get() == stellen_wort:
                            tk.messagebox.showinfo("Hangman", "Du hast gewonnen!")
                            spielen()

            else:
                versuche += 1
                print(versuche)
                imgLabel.config(image=bilder[versuche])
                if versuche == leben:
                    tk.messagebox.showinfo("Hangman", "Du hast leider verloren.")

        imgLabel = tk.Label(self)
        imgLabel.grid(row= 0, column= 0, columnspan=3, padx=10, pady=40)
        imgLabel.config(image=bilder[0])

        lblWord = tk.StringVar()  #Striche, die für die Stellen der Buchstaben stehen
        tk.Label(self, textvariable=lblWord, font="Consolas 24 bold").grid(row=0, column=3, columnspan=6, padx=10)

        txtWord = tk.StringVar()
        tk.Entry(self, textvariable=txtWord, font="Consolas 24 bold").grid(row=1, column=6, columnspan=2, padx=10)
        button = tk.Button(self, text='Enter', command=erraten)
        button.grid(row=2, column=6, columnspan=2, padx=10)
        button.focus_set()
        self.bind("<Return>", erraten)
        spielen()


class Hangman(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        # Fenster getsalten
        window = tk.Frame(self)
        window.pack()

        window.grid_rowconfigure(0, minsize=500)
        window.grid_columnconfigure(0, minsize=800)

        self.frames = {}
        for F in (MainPage, SecondPage, ThirdPage):
            frame = F(window, self)
            self.frames[F] = frame
            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(MainPage)

    def show_frame(self, page):
        frame = self.frames[page]
        frame.tkraise()
        self.title("Hangman")


app = Hangman()
app.maxsize(900, 600)
app.mainloop() 
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@luftblumexo: Ihr solltet erst einmal einen Schritt zurück gehen und die Grundlagen bis einschliesslich objektorientierter Programmierung lernen. Es nützt nichts (schlechten) Code von irgendwoher zu kopieren, den Ihr dann nicht versteht.

``global`` und lokale Funktionen, also ``def`` innerhalb von ``def`` am besten gleich wieder vergessen. Für ``global`` gibt es keinen Grund/keine Ausrede wenn man Klassen hat, und auch ohne Klassen sollte man das nicht verwenden. Also ``global`` wirklich gründlich aus dem Gedächtnis entfernen. Und verschachtelte Funktionen haben manchmal einen Sinn, aber ganz sicher nicht hier.

Das mit den mehreren Seiten in einem Fenster ist IMHO schlecht gelöst und ich würde das auch nicht empfehlen für Anfänger. Macht ein Fenster mit dem Spiel selbst, und Buttons für Spiel (neu) starten und Anleitung. Letzteres öffnet dann eine Messagebox mit der Anleitung. Damit wird der Code deutlich einfacher verständlich.

Es wird `messagebox` aus `tkinter` importiert, aber nicht direkt verwendet, aber über `tk.messagebox`. Das ist verwirrend weil `messagebox` nicht verwendet wird, der Import aber notwendig ist, weil es sonst auch nicht über `tk.messagebox` verfügbar ist.

Der Import von `Image` aus `PIL` wird nirgends verwendet.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase). Siehe auch den Style Guide for Python Code

Grunddatentypen haben nichts in Namen verloren. Den Typen ändert man gar nicht so selten mal während der Programmentwicklung und dann muss man überall im Programm die betroffenen Namen ändern, oder man hat falsche, irreführende Namen im Quelltext.

Namen werden nicht durchnummeriert. Dann will man entweder bessere Namen verwenden, oder gar keine Einzelnamen sondern eine Datenstruktur. Oft eine Liste. So auch im Falle von `img1` bis `img8`. Auch sollte man so etwas nicht kryptisch abkürzen. Wenn man `image` meint, sollte man nicht nur `img` schreiben.

Code: Alles auswählen

        img1 = PIL.Image.open("3.png")
        img2 = PIL.Image.open("4.png")
        img3 = PIL.Image.open("5.png")
        img4 = PIL.Image.open("6.png")
        img5 = PIL.Image.open("7.png")
        img6 = PIL.Image.open("8.png")
        img7 = PIL.Image.open("9.png")
        img8 = PIL.Image.open("10.png")
        bilder = [
            ImageTk.PhotoImage(img1),
            ImageTk.PhotoImage(img2),
            ImageTk.PhotoImage(img3),
            ImageTk.PhotoImage(img4),
            ImageTk.PhotoImage(img5),
            ImageTk.PhotoImage(img6),
            ImageTk.PhotoImage(img7),
            ImageTk.PhotoImage(img8),
        ]
        
        # =>
        
        bilder = [
            ImageTk.PhotoImage(PIL.Image.open(f"{i}.png"))
            for i in range(3, 11)
        ]
Auch das anhängen von ”aufsteigenden” Buchstaben an Namen ist ein „code smell“. Wenn man die Namen letztlich gar nicht mehr braucht, kann man auch immer den gleichen Namen verwenden, oder auch gar keinen, wenn man alles als *eine* Anweisung schreiben kann.

Wenn es im `tkinter`-Modul eine passende Konstante für eine Zeichenkette gibt, sollte man die verwenden. Beispielweise beim `side`-Argument von `pack()` nicht "bottom" sondern `tk.BOTTOM`.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Wofür steht das `lbl` in `lblWord`? Das ist ja schon mal kein `Label` sondern ein `StringVar`-Objekt.

`leben` ist redundant. Das ist die Länge der `bilder`-Liste. Da kann man das von abfragen und vermeidet so mögliche Arbeit und Fehler wenn man die Anzahl der Bilder mal ändern sollte.

Wenn man wissen möchte ob ein Zeichen in einer Zeichenkette vorkommt, ist es effizienter genau das direkt zu testen, statt die vorkommen der Zeichen zu zählen. Also ``character in text`` statt ``text.count(character) > 0``.

Ich würde die Programmlogik von der GUI trennen und mit der Programmlogik anfangen. Also eine Klasse die das Hangman-Spiel implementiert und auf die man dann, wenn das funktioniert und getestet ist, eine GUI drauf setzt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten