Seite 1 von 1

Einteilung von GUI und Spiellogik durch Klassen

Verfasst: Donnerstag 10. Oktober 2019, 13:38
von Sergeant

Code: Alles auswählen

from random import randint
from tkinter import *


#Definitionen

def Button_Einsatz_Click():
    # Verwaltung der Daten
    ButtonEinsatz.config(state = DISABLED)
    radiobutton1.config(state = NORMAL)
    radiobutton2.config(state = NORMAL)
    radiobutton3.config(state = NORMAL)
    radiobutton4.config(state = NORMAL)
    radiobutton5.config(state = NORMAL)
    radiobutton6.config(state = NORMAL)
    global konto
    konto = int(labelKonto.cget('text'))
    # Verarbeitung der Daten
    konto = konto - 1
    # Anzeige der Daten
    labelKonto.config(text=str(konto))
    
    
def Button_Würfel_Click():
    # Verwaltung der Daten
    ButtonGewinn.config(state = NORMAL)
    ButtonWürfel.config(state = DISABLED)
    global Würfel1
    global Würfel2
    global Würfel3
    
    Würfel1 = int(label1Würfel.cget('text'))
    Würfel2 = int(label2Würfel.cget('text'))
    Würfel3 = int(label3Würfel.cget('text'))
    # Verarbeitung der Daten
    Würfel1 = randint(1, 6)
    Würfel2 = randint(1, 6)
    Würfel3 = randint(1, 6)
    # Anzeige der Daten
    label1Würfel.config(text=str(Würfel1))
    label2Würfel.config(text=str(Würfel2))
    label3Würfel.config(text=str(Würfel3))

def Button_Gewinn_Click():
    #Verwaltung der Daten
    ButtonEinsatz.config(state = NORMAL)
    ButtonGewinn.config(state = DISABLED)
    global labelAusgewaehlteZahl1
    labelAusgewaehlteZahl1 = labelAusgewaehlteZahl.cget("text")
    #Verarbeitung der Daten
    global konto
    global Würfel1
    global Würfel2
    global Würfel3
    
    if int(labelAusgewaehlteZahl1) == int(Würfel1) and int(labelAusgewaehlteZahl1) == int(Würfel2) and int(labelAusgewaehlteZahl1) == int(Würfel3):
        konto = konto + 3
        print(konto, "der Gewinn ist:3")
    elif int(labelAusgewaehlteZahl1) == int(Würfel1) and int(labelAusgewaehlteZahl1) == int(Würfel2) or int(labelAusgewaehlteZahl1) == int(Würfel1) and int(labelAusgewaehlteZahl1) == int(Würfel3) or int(labelAusgewaehlteZahl1) == int(Würfel2) and int(labelAusgewaehlteZahl1) == int(Würfel3):
        konto = konto + 2
        print(konto, "der Gewinn ist:2")
    elif int(labelAusgewaehlteZahl1) == int(Würfel1) or int(labelAusgewaehlteZahl1) == int(Würfel2) or int(labelAusgewaehlteZahl1) == int(Würfel3):
        konto = konto + 1
        print(konto, "der Gewinn ist:1")
    #Anzeige der Daten
    labelKonto.config(text=str(konto))

    
def Radiobutton_Click():
    # Verwaltung der Daten
    ButtonWürfel.config(state = NORMAL)
    radiobutton1.config(state = DISABLED)
    radiobutton2.config(state = DISABLED)
    radiobutton3.config(state = DISABLED)
    radiobutton4.config(state = DISABLED)
    radiobutton5.config(state = DISABLED)
    radiobutton6.config(state = DISABLED)
    zahl = spielzahl.get()
    # Anzeige der Daten
    labelAusgewaehlteZahl.config(text=str(zahl))

#------------------------------------------------------------------

#Die Grafik


# Erzeugung des Fensters
tkFenster = Tk()
tkFenster.title("Test")
tkFenster.geometry('800x500')
# Label mit Gesamtüberschrift
labelGesamtüberschrift = Label(master=tkFenster,
                                text="chuck a luck",
                                background="#E6E6FA")
labelGesamtüberschrift.place(x=0, y=0, width=800, height=30)
# Label mit Überschrift für das Konto
labelUeberschriftKonto = Label(master=tkFenster,
                                text="Konto",
                                background="#FFCFC9")
labelUeberschriftKonto.place(x=10, y=40, width=100, height=50)
# Label für den Kontostand
labelKonto = Label(master=tkFenster,
                    text="100",
                    background="#FFCFC9")
labelKonto.place(x=28, y=110, width=60, height=60)
# Label mit der Überschrift für die Würfel
labelUeberschriftWürfel = Label(master=tkFenster,
                                text="Würfel",
                                background="#FBD975")
labelUeberschriftWürfel.place(x=550, y=40, width=200, height=50)
# Label für den 1Würfel
label1Würfel = Label(master=tkFenster,
                    text="1",
                    background="#FBD975")
label1Würfel.place(x=550, y=110, width=60, height=60)
# Label für den 2Würfel
label2Würfel = Label(master=tkFenster,
                    text="2",
                    background="#FBD975")
label2Würfel.place(x=620, y=110, width=60, height=60)
# Label für den 3Würfel
label3Würfel = Label(master=tkFenster,
                    text="3",
                    background="#FBD975")
label3Würfel.place(x=690, y=110, width=60, height=60)

# Button zum Auswerten
ButtonEinsatz = Button(master=tkFenster,
                        text="Einsatz zahlen",
                        command=Button_Einsatz_Click)
ButtonEinsatz.place(x=10, y=180, width=100, height=20)
ButtonEinsatz.config(state = NORMAL)
#Button zum Würfel werfen
ButtonWürfel = Button(master=tkFenster,
                       text="Würfel werfen",
                       command=Button_Würfel_Click)
ButtonWürfel.config(state = DISABLED)
ButtonWürfel.place(x=600, y=180, width=100, height=20)

#Label mit Überschrift für die Zahl
labelUeberschriftZahl = Label(master=tkFenster,
                                text="Zahl",
                                background="#D5E88F")
labelUeberschriftZahl.place(x=200, y=40, width=300, height=50)

# Label mit ausgewählter Zahl
labelAusgewaehlteZahl = Label(master=tkFenster,
                              text="",
                              background="white")
labelAusgewaehlteZahl.place(x=335, y=145, width=30, height=18)

# Radiobutton für die Zahl

spielzahl = IntVar()
radiobutton1 = Radiobutton(master=tkFenster,
                           text='1',
                           value=1,
                           variable=spielzahl,
                           command=Radiobutton_Click)
radiobutton1.config(state = DISABLED)
radiobutton1.place(x=200, y=100, width=30, height=18)
radiobutton2 = Radiobutton(master=tkFenster,
                           text='2',
                           value=2,
                           variable=spielzahl,
                           command=Radiobutton_Click)
radiobutton2.config(state = DISABLED)
radiobutton2.place(x=250, y=100, width=30, height=18)
radiobutton3 = Radiobutton(master=tkFenster,
                           text='3',
                           value=3,
                           variable=spielzahl,
                           command=Radiobutton_Click)
radiobutton3.config(state = DISABLED)
radiobutton3.place(x=300, y=100, width=30, height=18)
radiobutton4 = Radiobutton(master=tkFenster,
                           text='4',
                           value=4,
                           variable=spielzahl,
                           command=Radiobutton_Click)
radiobutton4.config(state = DISABLED)
radiobutton4.place(x=350, y=100, width=30, height=18)
radiobutton5 = Radiobutton(master=tkFenster,
                           text='5',
                           value=5,
                           variable=spielzahl,
                           command=Radiobutton_Click)
radiobutton5.config(state = DISABLED)
radiobutton5.place(x=400, y=100, width=30, height=18)
radiobutton6 = Radiobutton(master=tkFenster,
                           text='6',
                           value=6,
                           variable=spielzahl,
                           command=Radiobutton_Click)
radiobutton6.config(state = DISABLED)
radiobutton6.place(x=450, y=100, width=30, height=18)

#Button für Gewinn auszahlen
ButtonGewinn = Button(master=tkFenster,
                        text="Gewinn auszahlen",
                        command=Button_Gewinn_Click)
ButtonGewinn.config(state = DISABLED)
ButtonGewinn.place(x=300, y=180, width=100, height=20)


# Aktivierung des Fensters
tkFenster.mainloop()
Wir haben folgenden Arbeitsauftrag bekommen:
Bewertet werden die folgenden Aspekte:
1. Funktioniert das Spiel? (Chuck-a-luck oder eine eigene Entwicklung!)
2. Reagiert die GUI auf Klicks/Eingaben? (Verändert sich etwas?)
3. Gibt es eine saubere Trennung zwischen Spielelogik und GUI-Funktionen?
4. Wurde eine zweckmäßige, einfache Klassenstruktur verwendet?
Das Programm funktioniert soweit ganz gut doch wir bekommen die Einteilung der GUI und der Spiellogik in einer Klassenstruktur nicht hin. Wir bitten um Hilfe!

Re: Einteilung von GUI und Spiellogik durch Klassen

Verfasst: Donnerstag 10. Oktober 2019, 13:44
von __deets__
Ich sehe da nicht, das ihr etwas hinkriegen muesst. Da steht doch nur die Frage, ob der Code auf bestimmte Art beschaffen ist. Wurde eine zweckmaessig, einfache Klassenstruktur verwendet? Was denkst du?

Re: Einteilung von GUI und Spiellogik durch Klassen

Verfasst: Donnerstag 10. Oktober 2019, 15:53
von Sirius3
@__deets__: ich denke, wenn nur das Spiel funktioniert (wobei funktionieren doch wohl Punkt 2 einschließt) dann gibt es halt nur eine schlechte Note, wenn man dagegen mit zweckmäßigen, einfachen Klassenstrukturen programmiert, dann gibt es eine guute Note.

Re: Einteilung von GUI und Spiellogik durch Klassen

Verfasst: Donnerstag 10. Oktober 2019, 15:59
von Sirius3
@Sergeant: bevor man hier anfängt, Logik von GUI zu trennen, oder Klassen einzuführen, muß erst einmal der Code aufgeräumt werden, das bedeutet, den *-Import durch `import tkinter as tk` ersetzen; alle `global` zu entfernen und den Funktionen, alles was sie brauchen per Parameter übergeben; die Namenskonvention umsetzen, dass Funktionsnamen und Variablennamen klein_mit_unterstrich geschrieben werden; alles ab "Die Grafik" in eine Funktion packen, die `main` heißt und zum Schluß per `if __name__ == '__main__': main()` aufgerufen wird.

Gleichzeitig sollten die durchnummerierten Variablen durch Listen ersetzt werden, der Code dadurch per for-Schleifen von doppeltem Code entschlacken.

Weißt Du, was es bedeutet Logik von GUI zu trennen?

Re: Einteilung von GUI und Spiellogik durch Klassen

Verfasst: Donnerstag 10. Oktober 2019, 16:03
von __deets__
@Sirius3: ah, ich habe das irgendwie falsch interpretiert. Es klang nach "das ist der Code, das sind die Kritierien, genuegt der Code denen?". Aber wenn der selbst gebaut ist - klar. Dann ist er nicht so dolle...

Re: Einteilung von GUI und Spiellogik durch Klassen

Verfasst: Donnerstag 10. Oktober 2019, 16:21
von Sirius3
@Sergeant: anderer Ansatz wäre, alles zu löschen und mit der Logik als Klasse neu anzufangen, ohne GUI. Dazu die Fragen beantworten:
Wie wird der Zustand des Spiels komplett beschrieben? Das sind die Attribute, die in __init__ angelegt werden müssen.
Wie kann der Zustand des Spiels geändert werden? Das sind die Methoden der Klasse.

Re: Einteilung von GUI und Spiellogik durch Klassen

Verfasst: Donnerstag 24. Oktober 2019, 12:48
von __blackjack__
@Sergeant: Ergänzend zu dem was Sirius3 schon schrieb:

Der Code sieht verdächtig nach dem Stil von https://www.inf-schule.de aus. Der ist nicht gut, das sollte man sich nicht als Vorbild nehmen. Die mögen dort Sternchenimporte, Namen die sich nicht an die Python-Konventionen halten, ``global``, und `place()`. Auf der anderen Seite wird das dann auch gerne mal mit Klassenwahnsinn wie in Java vermischt. Also nicht entweder oder, sondern beides im gleichen Programm!

Im Grunde würde ich empfehlen das Programm als Prototypen zu betrachten und noch mal komplett neu anzufangen. Und zwar mit der Programmlogik, ohne GUI. Und erst wenn die Programmlogik als Klasse steht und funktioniert, da dann eine GUI drauf setzen. So kommt man gar nicht erst in die Versuchung GUI und Programmlogik zu vermischen.

Und in der GUI dann auch nicht `place()` verwenden. Das gezeigte Programm sieht bei dem Rechner an dem ich gerade sitze beispielsweise so aus:
Bild

Die Zahlen bei den Radio-Buttons sind halb abgeschnitten und „Gewinn auszahlen“ ist nicht komplett lesbar. Es nützt auch nichts da mehr absoluten Platz zu lassen — bei der Vielfalt von Anzeigegrössen und -auflösungen wird es immer irgendwo Probleme damit geben. Mit `pack()` oder `grid()`, gerne auch noch mal in `Frame`\s untergliedert, bekommt jedes Widget den Platz den es braucht, und man muss/sollte auch keine Fenstergesamtgrösse mehr vorgeben und hat dann auch nicht so viel überflüssigen Platz unten im Fenster.

Von der Bedienung her finde ich es negativ überraschend das man bei den Radiobuttons durch die erste Auswahl einer Zahl die Radiobuttons deaktiviert. Das ist so ganz und gar nicht das normale Verhalten das man von anderen Anwendung her kennt. Ich sehe auch nicht so ganz warum die überhaupt jemals deaktiviert sein müssen. Das grenzt an Gängelung des Benutzers. Das einzige was man per GUI erzwingen muss, ist das der Benutzer vor dem Würfeln einen Einsatz gemacht hat.

Der „Gewinn auszahlen“-Button ist auch nicht sinnvoll. Das ist ja das einzige was man an der Stelle machen kann. Dann kann das auch ohne extra Klick einfach automatisch passieren.

Re: Einteilung von GUI und Spiellogik durch Klassen

Verfasst: Freitag 8. November 2019, 11:40
von __blackjack__
@Sergeant: Mir ist noch aufgefallen das das Konto nur einen, ich sag mal informierenden Character hat. Ich hätte da ja erwartet das man nicht mehr spielen kann wenn das Konto leer ist, aber das geht dann ja einfach beliebig weit ins negative weiter.

Hier mal ein Ansatz:

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial
from random import randint
import tkinter as tk
from tkinter.font import Font

DIE_FACE_COUNT = 6
FIRST_DIE_FACE_CODEPOINT = ord("⚀")


roll_die = partial(randint, 1, DIE_FACE_COUNT)


def roll_dice(count):
    return [roll_die() for _ in range(count)]


class ChuckALuck:
    def __init__(self, balance=100, die_count=3):
        self.balance = balance
        self.die_count = die_count
        self.stakes = 0

    def pay_stakes(self, amount=1):
        if self.balance < amount:
            raise ValueError("insufficient funds")
        if self.stakes != 0:
            raise ValueError("stakes already paid")
        self.balance -= amount
        self.stakes += amount

    def roll_dice(self, chosen_number):
        self.stakes = 0
        result = roll_dice(self.die_count)
        points = result.count(chosen_number)
        self.balance += points
        return result


class ChuckALuckFrame(tk.Frame):
    def __init__(self, parent, game):
        tk.Frame.__init__(self, parent)
        self.game = game

        self.balance_var = tk.IntVar()
        self.face_choice_var = tk.IntVar(value=1)

        tk.Label(self, text="chuck a luck", background="#E6E6FA").pack(
            fill=tk.X
        )

        frame = tk.Frame(self)
        frame.pack()

        header_options = dict(sticky=tk.NSEW, ipady=15, padx=5, pady=5)
        color = "#FFCFC9"
        tk.Label(frame, text="Konto", background=color).grid(
            column=0, row=0, **header_options
        )
        tk.Label(frame, textvariable=self.balance_var, background=color).grid(
            column=0, row=1
        )
        self.payment_button = tk.Button(
            frame, text="Einsatz zahlen", command=self.do_pay
        )
        self.payment_button.grid(column=0, row=2)

        color = "#D5E88F"
        tk.Label(frame, text="Zahl", background=color).grid(
            column=1, row=0, **header_options
        )

        choices_frame = tk.Frame(frame)
        choices_frame.grid(column=1, row=1)

        radiobutton_frame = tk.Frame(choices_frame)
        radiobutton_frame.pack()
        for face_value in range(1, DIE_FACE_COUNT + 1):
            tk.Radiobutton(
                radiobutton_frame,
                text=face_value,
                variable=self.face_choice_var,
                value=face_value,
            ).pack(side=tk.LEFT)

        color = "#E6C460"
        tk.Label(frame, text="Würfel", background=color).grid(
            column=2, row=0, **header_options
        )

        dice_frame = tk.Frame(frame)
        dice_frame.grid(column=2, row=1)

        dice_font = Font().copy()
        dice_font["size"] = dice_font.actual("size") * 3

        self.die_labels = list()
        for i in range(self.game.die_count):
            label = tk.Label(
                dice_frame,
                text=chr(FIRST_DIE_FACE_CODEPOINT),
                font=dice_font,
                foreground=color,
                background=color,
            )
            label.grid(
                row=0, column=i, sticky=tk.NSEW, ipadx=12, ipady=12, padx=2
            )
            self.die_labels.append(label)

        self.roll_button = tk.Button(
            frame, text="Würfel werfen", command=self.do_roll
        )
        self.roll_button.grid(column=2, row=2)

        self.update_display()

    def update_display(self):
        self.balance_var.set(self.game.balance)
        if self.game.stakes != 0:
            self.payment_button["state"] = tk.DISABLED
            self.roll_button["state"] = tk.NORMAL
        else:
            self.payment_button["state"] = (
                tk.NORMAL if self.game.balance > 0 else tk.DISABLED
            )
            self.roll_button["state"] = tk.DISABLED

    def do_pay(self):
        self.game.pay_stakes()
        self.update_display()

    def do_roll(self):
        chosen_number = self.face_choice_var.get()
        die_values = self.game.roll_dice(chosen_number)
        for die_value, die_label in zip(die_values, self.die_labels):
            die_label.configure(
                text=chr(FIRST_DIE_FACE_CODEPOINT + die_value - 1),
                foreground="white" if die_value == chosen_number else "black",
            )
        self.update_display()


def main():
    chuck_a_luck = ChuckALuck()

    root = tk.Tk()
    root.title("Chuck A Luck")
    chuck_a_luck_frame = ChuckALuckFrame(root, chuck_a_luck)
    chuck_a_luck_frame.pack()
    root.mainloop()


if __name__ == "__main__":
    main()