Codeknacker | Python-Kurs | Tkinter |
Zur Begleitung des Python-Kurses wird das Spiel Codeknacker implementiert. Zuletzt wurde es in einer Klasse zusammengefasst, die nun benutzt werden kann, um in einem Fenster gespielt zu werden.
Als grafische Oberfläche wird die Tk-Version tkinter verwendet, die quasi den Standard für Python darstellt.
Erstellung eines Fensters
Die hier vorgestellte Version ist recht einfach gehalten. Der Spieler tippt seinen vierstelligen Versuch ein, klickt auf den Button und erhält eine Bewertung der Treffer. Damit er sich das nicht merken muss, werden diese in einer Liste abgelegt, damit der Spieler aus den bisherigen Versuchen kombinieren kann, welche Geheimzahl sich der Computer wohl ausgedacht hat.Wir benötigen ein Fenster mit mehreren Kontrollementen.
- Ein Eingabefeld für das Raten des Codes.
- Ein Button, der das Programm zur Auswertung des Rateversuchs anstiftet.
- Ein Label, in dem die Zahl der Rateversuche steht
- Eine Liste der bisherigen Rateversuche.
Das lässt sich durch folgendes Programm herstellen:
import tkinter fenster = tkinter.Tk() fenster.title("Code-Knacker") ctrlframe = tkinter.Frame(fenster) ctrlframe.pack(expand=True, fill=tkinter.X) eingabe = tkinter.Entry(ctrlframe) eingabe.pack(expand=True, side=tkinter.LEFT, fill=tkinter.BOTH) knopf = tkinter.Button(ctrlframe, text = "Raten") knopf.pack(expand=False, side=tkinter.LEFT) label = tkinter.Label(ctrlframe, text="0") label.pack(expand=False, side=tkinter.LEFT) listframe = tkinter.Frame(fenster) scrollbar = tkinter.Scrollbar(listframe) scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y) listbox = tkinter.Listbox(listframe, yscrollcommand=scrollbar.set) listbox.pack(side=tkinter.LEFT, expand=True, fill=tkinter.BOTH) scrollbar.config(command=listbox.yview) listframe.pack(expand=True, fill=tkinter.X) fenster.mainloop()
- Der Rahmen besteht aus einem Fenster, das durch Aufruf von Tk() erzeugt wird.
- Dort hinein wird ein Frame gelegt, also ein unsichtbarer Rahmen, der das Eingabefeld (entry), den Button und das Label in der oberen Zeile anordnet.
- In einem weiteren Frame wird die Listbox mit dem sie verwaltenden Schiebebalken (Scrollbar) angeordnet.
- Zu guter Letzt sorgt die mainloop für den Start des Fensters.
Das sieht schon gut aus, tut aber nichts. Man kann eingeben, was man will, kann den Button drücken. Es passiert nichts. Nur das Anklicken des Kreuzes rechts oben führt zum Ende des Programms. Andererseits ist das Ergebnis für ein Programm von rund 20 Zeilen ja schon recht ansehnlich.
Aktion durch den Button
Das starre Fenster muss in Bewegung geraten. Die Eingabe in das Entry kann bereits erfolgen. Das Auslesen sollte dann passieren, wenn der Button gedrückt wird.Für die Reaktion auf den Button wird eine sogenannte Callback-Funktion definiert. Sie wird vom Fenster zurückgerufen, wenn der Button gedrückt wird.
Damit der Button auch weiß, dass er eine Callback-Funktion hat, wird diese bei Aufruf des Konstruktors als Parameter command übergeben.
def btnRead(): txt = eingabe.get() knopf = tkinter.Button(ctrlframe, text = "Raten", command=btnRead)
Die Callback-Funktion übernimmt die zentrale Rolle im Spielablauf.
- Sie liest den String im Eingabefeld aus und leert dieses anschließend.
- Die Eingabe kann mit der Methode vergleiche der Klasse Codeknacker in Treffer und Enthaltene umgerechnet werden.
- Rateversuch und das Ergebnis von vergleiche wird hinten in der Listbox angehängt.
- Die Anzahl der Rateversuche wird hochgezählt.
- Ein bisschen schummeln kann während der Entwicklung nicht schaden. Die Geheimzahl wird neben der Anzahl der Rateversuche in den Label mit eingeblendet.
import tkinter import codeknacker ratezahl = 0 listbox = None ck = codeknacker.CodeKnacker() def btnRead(): global ratezahl, ck, listbox txt = eingabe.get() eingabe.delete(0, tkinter.END) pos, enth = ck.vergleiche(txt) ratezahl += 1 listeneintrag = "{} Pos: {} In: {}".format(txt, pos, enth) listbox.insert(tkinter.END, listeneintrag) label["text"] = "{} {}".format(ck.geheim,str(ratezahl)) fenster = tkinter.Tk() fenster.title("Code-Knacker") ctrlframe = tkinter.Frame(fenster) ctrlframe.pack(expand=True, fill=tkinter.X) eingabe = tkinter.Entry(ctrlframe) eingabe.pack(expand=True, side=tkinter.LEFT, fill=tkinter.BOTH) knopf = tkinter.Button(ctrlframe, text = "Raten", command=btnRead) knopf.pack(expand=False, side=tkinter.LEFT) label = tkinter.Label(ctrlframe, text="0") label.pack(expand=False, side=tkinter.LEFT) listframe = tkinter.Frame(fenster) scrollbar = tkinter.Scrollbar(listframe) scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y) listbox = tkinter.Listbox(listframe, yscrollcommand=scrollbar.set) listbox.pack(side=tkinter.LEFT, expand=True, fill=tkinter.BOTH) scrollbar.config(command=listbox.yview) listframe.pack(expand=True, fill=tkinter.X) fenster.mainloop()Da einige Variablen außerhalb der Callback-Funktion in dieser verändert werden, müssen diese gleich zu Anfang global gekennzeichnet werden.
Siegerehrung
Das Spiel ist gewonnen, wenn alle vier Ziffern an der richtigen Position sind. Dazu muss in btnRead das Ergebnis von pos mit 4 verglichen werden.
- Danach muss erst einmal der Spieler gelobt werden. Das übernimmt eine Messagebox.
- Es muss eine neue Geheimzahl gezogen werden. Das erreicht man am einfachsten, indem ein neues Objekt der Klasse CodeKnacker erzeugt wird.
- Die Listbox muss geleert werden.
- Die Anzahl der Rateversuche muss wieder auf 0 gesetzt werden.
if pos==4: tkmsg.Message(master=fenster,message="Gewonnen in {} Versuchen".format(ratezahl)).show() ck = codeknacker.CodeKnacker() ratezahl = 0 listbox.delete(0,tkinter.END) label["text"] = "{} {}".format(ck.geheim,str(ratezahl))
Eingabe mit Drops
Problematisch ist die Eingabe. Der Benutzer kann statt eines vierstelligen Zifferncode zwischen 1 und 6 seinen Namen oder zuwenige Ziffern eintippen. In jedem dieser Fälle stürzt das Programm ab. Natürlich kann man versuchen die Fehler abzufangen und den Benutzer ermahnen, seine Rolle doch bitte ernst zu nehmen.Mit der Hilfe von Drop-Down-Elementen oder Comboboxen kann man erreichen, dass der Benutzer gar nichts anderes eingeben kann. Man stellt vier Comboboxen auf, füllt sie mit den Ziffern von 1 bis 6 und sorgt für eine Vorauswahl. Und schon gibt es keine Eingabefehler mehr.
Ein schöner Nebeneffekt: Das Spiel wird allein durch die Maus bedient oder - bei einem Touch-Screen nur durch den Finger.
Einbau der Comboboxen
Für die Verwendung einer Combobox muss ttk von tkinter importiert werden. Dann kann eine Combobox erstellt werden, genau wie ein anderes Kontrollelement. Wir brauchen vier davon, also bauen wir die Erzeugung in eine Schleife ein.
Als Parameter wird state auf readonly geändert, so dass der Benutzer nichts eintippen, sondern nur aus den Vorgaben auswählen kan. Die Breite width wird etwas verknappt, damit sie nicht so viel Platz verbrauchen und schließlich wird als values eine Liste der sechs möglichen Ziffern übergeben.
Der Aufruf von current sorgt dafür, dass die erste Auswahl schon selektiert ist. Danach kann mit der Combobox nur noch ein gültiges Element ausgewählt werden.
Und natürlich müssen die vier Elemente zusammengepackt werden.
ziffern = ["1", "2", "3", "4", "5", "6"] combo = [] for i in range(4): combo.append(ttk.Combobox(ctrlframe, width=2, state="readonly", values=ziffern)) combo[i].current(0) combo[i].pack(expand=False, side=tkinter.LEFT)
Im Callback
In der Callback-Funktion des Buttons müssen die ausgewählten Combobox-Inhalte zu einem String zusammengesetzt werden. Danach kann man genauso fortfahren wie bei der Eingabe per Entry.
def btnRead(): global ratezahl, ck, listbox ratestr = "" for i in range(4): ratestr += combo[i].get()Im Gegensatz zu der Methode current liefert die Methode get den String, der in der Selektion steht. Wenn man alle vier aneinander hängt, erhält man den Ratestring, so wie man ihn an die vergleich-Methode der Klasse CodeKnacker senden kann.
Und nun alles zusammen
Hier setzen wir alles zusammen. Das Programm ist gar nicht erheblich länger geworden.import tkinter import tkinter.messagebox as tkmsg from tkinter import ttk import codeknacker ratezahl = 0 listbox = None ck = codeknacker.CodeKnacker() def btnRead(): global ratezahl, ck, listbox ratestr = "" for i in range(4): ratestr += combo[i].get() pos, enth = ck.vergleiche(ratestr) ratezahl += 1 listeneintrag = "{} Pos: {} In: {}".format(ratestr, pos, enth) listbox.insert(tkinter.END, listeneintrag) label["text"] = "{} {}".format(ck.geheim,str(ratezahl)) # Für die entgülte Spielversion ändern in #label["text"] = str(ratezahl) if pos==4: tkmsg.Message(master=fenster, message="Gewonnen in {} Versuchen".format(ratezahl)).show() ck = codeknacker.CodeKnacker() ratezahl = 0 listbox.delete(0,tkinter.END) label["text"] = "{} {}".format(ck.geheim,str(ratezahl)) fenster = tkinter.Tk() fenster.title("Code-Knacker") ctrlframe = tkinter.Frame(fenster) ctrlframe.pack(expand=True, fill=tkinter.X) ziffern = ["1", "2", "3", "4", "5", "6"] combo = [] for i in range(4): combo.append(ttk.Combobox(ctrlframe, width=2, state="readonly", values=ziffern)) combo[i].current(0) combo[i].pack(expand=False, side=tkinter.LEFT) knopf = tkinter.Button(ctrlframe, text = "Raten", command=btnRead) knopf.pack(expand=False, side=tkinter.LEFT) label = tkinter.Label(ctrlframe, text="0") label.pack(expand=False, side=tkinter.LEFT) listframe = tkinter.Frame(fenster) scrollbar = tkinter.Scrollbar(listframe) scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y) listbox = tkinter.Listbox(listframe, yscrollcommand=scrollbar.set) listbox.pack(side=tkinter.LEFT, expand=True, fill=tkinter.BOTH) scrollbar.config(command=listbox.yview) listframe.pack(expand=True, fill=tkinter.X) fenster.mainloop()
Und so sieht das Spiel auf dem Bildschirm aus: