Tic Tac Toe Spielfeld
Allgemein,  Programmieren

Tic Tac Toe-Spiel in Python

Ein einfaches Tic Tac Toe-Spiel mit grafischer Benutzeroberfläche für zwei Spieler. Dabei zeigt das Fenster neben dem Spielfeld den aktuellen Spieler an, sowie den Ausgang des Spiels und einer Revanche-Nachfrage. Mehr Features gibt’s in meinem nächsten Beitrag “Upgrade: Neue Features für das Tic Tac Toe-Spiel“.

Das alles wird mit Python und dessen Bibliothek tkinter umgesetzt.

Grundvoraussetzungen

  • Rechner, auf dem python3 installiert ist
  • Entwicklungsumgebung, die mit Python umgehen kann
    • IDLE – Python-Entwicklungsumgebung, die je nach Installation von Python gleich mit installiert wird (Download der neuesten Version von python.org)
    • Visual Studio Code mit Python-Plugin – weit verbreitete OpenSource-Entwicklungsumgebung, die die nötigen Plugins selbst vorschlägt und auf Nachfrage installiert
    • prinzipiell egal, welche Entwicklungsumgebung – das ist Geschmackssache 😉

Tic Tac Toe

Tic Tac Toe ist ein Brettspiel für zwei Personen mit simplem Aufbau. Beim “Spielbrett” handelt es sich um ein Raster, das 9 Felder eröffnet. Gespielt wird abwechselnd, wobei bei jedem Zug das Zeichen (meist X oder O) des jeweiligen Spielers in eines der Kästchen eingetragen wird. Gewonnen hat, wer zuerst 3 Zeichen in einer Reihe hat.

Die Ausgangsmöglichkeiten sehen zum Beispiel so aus:

XOO
X
X
Drei in einer Spalte
XXX
O
O
Drei in einer Reihe
XO
XO
X
Drei in einer Diagonale
XOX
XOX
OXO
Unentschieden

Aufbau des Spielfelds

Grundidee

Das Spielfeld besteht, wie oben bereits erläutert aus 9 gleichen Quadraten, in die das jeweilige Symbol eingetragen wird. Hier bietet es sich an, 9 quadratische Knöpfe zu erzeugen und diese in einem Raster anzuordnen. Dabei wird nach einem Klick auf den Knopf das Feld markiert. Da bei Tic Tac Toe immer abwechselnd gespielt wird, kann der Code nach einem Klick automatisch von der einen zur anderen Markierung springen.

Ein Fenster erzeugen

Die grafische Benutzeroberfläche soll nachher in einem Fenster erscheinen, das nach dem Start des Programms automatisch geöffnet wird. Dafür muss zunächst tkinter importiert werden.

import tkinter as tk

Anschließend wird das Objekt des Fensters erzeugt und durch die mainloop nach dem Programmstart geöffnet.

import tkinter as tk

window = tk.Tk()

window.mainloop()

Nun kann das Fenster noch an das Spiel angepasst werden. Es wird verhindert, dass das Fenster vergrößert oder verkleinert wird, sodass das Spielfeld zu sehen ist und nicht verzerrt wird. Außerdem wird der Titel des Fensters zu “Tic Tac Toe” geändert.

import tkinter as tk

window = tk.Tk()
window.resizable(False, False)
window.title("Tic Tac Toe")

window.mainloop()

Die Größe des Fensters wird nachher über die Größe der Knöpfe definiert.

Das Spielfeld einsetzen

Die Knöpfe werden in einen sogenannten Frame eingesetzt. Dadurch sind alle 9 Knöpfe zusammengefasst in einem Element, während die Anordnung der Knöpfe im Gitter einfach ermöglicht wird.

Daher wird zunächst der Frame erzeugt und mit der Funktion .pack() in das Fenster eingesetzt. Da der Frame noch nichts enthält ist zunächst nichts sichtbar – also nicht wundern ;).

import tkinter as tk

window = tk.Tk()
window.resizable(False, False)
window.title("Tic Tac Toe")

game_setup = tk.Frame(window)
game_setup.pack()

window.mainloop()

Knöpfe erzeugen und einsetzen

Damit nicht jeder Knopf einzeln erzeugt und eingesetzt werden muss, wird für diesen Zweck eine Klasse erstellt, die zudem die Parameter jedes einzelnen Knopfes festlegt. Dies sieht so aus:

import tkinter as tk

window = tk.Tk()
window.resizable(False, False)
window.title("Tic Tac Toe")

game_setup = tk.Frame(window)

class XO_tag:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.value = None
        self.button = tk.Button(
            game_setup,
            text = "",
            width = 10,
            height = 5,
            bg = "white",
            fg = "black",
            command = self.set_tag,
            )
        self.button.grid(row = x, column = y, padx = 2, pady = 2)

game_setup.pack()
window.mainloop()

Erläuterung: Es wird die Klasse XO_tag erstellt. Wenn ein neues Objekt dieser Klasse erstellt wird, wird die Funktion __init__(self, x, y) aufgerufen, wobei die Übergabeparameter x und y bei der Objekterzeugung übergeben werden müssen. Jedes Objekt dieser Klasse enthält die Attribute x, y, value und button. Dabei entsprechen x und y den Koordinaten des Knopfes im Raster des Spielfelds, während value dem Wert des Buttons (default ist None, nach einem Klick entspricht dieser X oder O) entspricht. Mit tk.Button() wird ein Knopf erstellt, dem wiederum verschiedene Attribute mitgegeben werden können, wobei es sich bei game_setup um den Frame handelt, in den der Knopf eingesetzt werden soll. Durch die Funktion .grid() des button wird der Knopf in das Gitter des Frames eingefügt, in die x-te Reihe und die y-te Spalte, während der Abstand in alle Richtung 2 entspricht.

Jetzt müssen alle Knöpfe erstellt werden, wobei die x– und y-Koordinaten übergeben werden. Dafür werden zwei for-Schleifen ineinander verschlungen.

import tkinter as tk

window = tk.Tk()
window.resizable(False, False)
window.title("Tic Tac Toe")

game_setup = tk.Frame(window)

class XO_tag:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.value = None
        self.button = tk.Button(
            game_setup,
            text = "",
            width = 10,
            height = 5,
            bg = "white",
            fg = "black",
            )
        self.button.grid(row = x, column = y, padx = 2, pady = 2)

for x in range(3):
    for y in range(3):
        XO_tag(x, y)

game_setup.pack()
window.mainloop()

Funktion der Knöpfe

Wenn ein Knopf gedrückt wird, soll das aktuelle Zeichen auf den Knopf geschrieben werden, sofern dieser noch nicht gedrückt wurde.

current_tag = "X"

def set_tag(self):
    global current_tag
    if self.value == None:
        self.value = current_tag

        if current_tag == "X":
            self.button.configure(text=current_tag, fg = "red")
            new_tag = "O"
        else:
            self.button.configure(text=current_tag, fg = "blue")
            new_tag = "X"
    current_tag = new_tag
    which_player.configure(text = 'Player ' + str(current_tag))

which_player = tk.Label(window, text = 'Player ' + str(current_tag))
which_player.pack()

Erläuterung: Zuerst wird das aktuelle Zeichen festgelegt, hier “X”, mit dem das Spiel begonnen wird. Wenn der Wert value des angeklickten Knopfes button None ist, dann wird dieser Wert mit dem aktuellen Zeichen überschrieben. Wenn “X” gerade an der Reihe ist, dann wird der Knopf mit einem roten “X” beschrieben, bei einem “O” dementsprechend mit einem blauen “O”. Je nach dem wird dann das aktuelle Zeichen auf das gegensätzliche Zeichen geändert. Zudem wird das Label, also die Beschriftung which_player nach jedem Klick aktualisiert und zeigt an, welcher Spieler am Zug ist.

Um die Funktion set_tag(self) aufzurufen, wird der button aus der Klasse über das Attribut command so modifiziert, dass der Knopf bei einem Klick diese Funktion aufruft. Dies geschieht über die folgende Zeile:

self.button = tk.Button(command = self.set_tag)

Der zusammengesetzte Code:

import tkinter as tk

window = tk.Tk()
window.resizable(False, False)
window.title("Tic Tac Toe")

game_setup = tk.Frame(window)
current_tag = "X"

class XO_tag:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.value = None
        self.button = tk.Button(
            game_setup,
            text = "",
            width = 10,
            height = 5,
            bg = "white",
            fg = "black",
            command = self.set_tag,
            )
        self.button.grid(row = x, column = y, padx = 2, pady = 2)

    def set_tag(self):
        global current_tag
        if self.value == None:
            self.value = current_tag
            if current_tag == "X":
                self.button.configure(text=current_tag, fg = "red")
                new_tag = "O"
            else:
                self.button.configure(text=current_tag, fg = "blue")
                new_tag = "X"

        current_tag = new_tag
        which_player.configure(text = 'Player ' + str(current_tag))

for x in range(3):
    for y in range(3):
        XO_tag(x, y)

which_player = tk.Label(window, text = 'Player ' + str(current_tag))

which_player.pack()
game_setup.pack()
window.mainloop()

Überprüfung der Ausgangsmöglichkeiten

Nach jedem Klick, also nach jeder gesetzten Markierung muss das Spiel auf die Ausgangsmöglichkeiten hin geprüft werden. Dabei muss überprüft werden, ob der Spieler gewonnen hat, also ob drei gleiche Markierungen in einer Reihe gesetzt wurden, oder ob das Spielfeld voll ist und das Spiel somit mit einem Unentschieden beendet ist.

Grundvoraussetzung

Um die eingetragenen Zeichen überprüfen zu können, müssen diese irgendwo eingetragen werden. Dafür eignet sich ein zweidimensionales Array, in das der jeweilige Wert nach einem Knopfdruck eingetragen wird. Dieses Array wird beispielsweise folgendermaßen initlialisiert:

XO_tags = [["-", "-", "-"],
           ["-", "-", "-"],
           ["-", "-", "-"]]

In dieser Darstellung ist die Struktur des zweidimensionalen Arrays am Besten erkennbar. Diese Methode ist für die Überprüfung eines Gewinns, bei dem man oft über dieses Array iterieren muss effizienter als wenn man versuchen würde, self.value auszulesen, da hierbei das ganze Objekt in dem Array gespeichert werden würde.

Nach jedem Klick wird die aktuelle Markierung an der entsprechenden Koordinate des Knopfes in das Array eingetragen.

def set_tag(self):
    global current_tag
    if self.value == None:
        self.value = current_tag
        XO_tags[self.x][self.y] = current_tag
        if current_tag == "X":
            self.button.configure(text=current_tag, fg = "red")
            new_tag = "O"
        else:
            self.button.configure(text=current_tag, fg = "blue")
            new_tag = "X"
    current_tag = new_tag
    which_player.configure(text = 'Player ' + str(current_tag))

Im bisherigen Code:

import tkinter as tk

window = tk.Tk()
window.resizable(False, False)
window.title("Tic Tac Toe")

game_setup = tk.Frame(window)
XO_tags = [["-", "-", "-"],
           ["-", "-", "-"],
           ["-", "-", "-"]]
current_tag = "X"

class XO_tag:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.value = None
        self.button = tk.Button(
            game_setup,
            text = "",
            width = 10,
            height = 5,
            bg = "white",
            fg = "black",
            command = self.set_tag,
            )
        self.button.grid(row = x, column = y, padx = 2, pady = 2)

    def set_tag(self):
        global current_tag
        if self.value == None:
            self.value = current_tag
            XO_tags[self.x][self.y] = current_tag
            if current_tag == "X":
                self.button.configure(text=current_tag, fg = "red")
                new_tag = "O"
            else:
                self.button.configure(text=current_tag, fg = "blue")
                new_tag = "X"

        current_tag = new_tag
        which_player.configure(text = 'Player ' + str(current_tag))

for x in range(3):
    for y in range(3):
        XO_tag(x, y)

which_player = tk.Label(window, text = 'Player ' + str(current_tag))

which_player.pack()
game_setup.pack()
window.mainloop()

Reihen überprüfen

Es müssen alle drei Reihen geprüft werden, wobei der aktuelle Spieler gewonnen hat, wenn seine Markierung in jedem Feld der Reihe gesetzt wurde. Das bedeutet, dass der Inhalt jedes Arrays der zweiten Dimension nur der aktuellen Markierung entsprechen darf. Die Gewinnmöglichkeiten im Array sehen folgendermaßen aus:

XO_tags = [["X", "X", "X"],
           ["-", "-", "-"],
           ["-", "-", "-"]]
XO_tags = [["-", "-", "-"],
           ["X", "X", "X"],
           ["-", "-", "-"]]
XO_tags = [["-", "-", "-"],
           ["-", "-", "-"],
           ["X", "X", "X"]]

Da man ja nicht die gesamte Überprüfung von Hand tippen möchte, sollte diese möglichst automatisch mit wenig Code umgesetzt werden. In diesem Fall bietet sich eine for-Schleife an. Damit wird dreimal – für jedes Array einmal – jedes Feld des Arrays mit der aktuellen Markierung verglichen. Wenn alle drei Felder dieser entsprechen, wird True zurückgegeben, also hat der Spieler gewonnen.

def check_rows(current_tag):
    for i in range(3):
        if (XO_tags[i][0] == current_tag) and (XO_tags[i][1] == current_tag) and (XO_tags[i][2] == current_tag):
            return True
    return False

Spalten überprüfen

Es müssen alle drei Reihen geprüft werden, wobei der aktuelle Spieler gewonnen hat, wenn seine Markierung in jedem Feld der Reihe gesetzt wurde. Das bedeutet, dass der Inhalt derselben Stelle jedes Arrays der zweiten Dimension der aktuellen Markierung entsprechen muss. Die Gewinnmöglichkeiten im Array sehen folgendermaßen aus:

XO_tags = [["X", "-", "-"],
           ["X", "-", "-"],
           ["X", "-", "-"]]
XO_tags = [["-", "X", "-"],
           ["-", "X", "-"],
           ["-", "X", "-"]]
XO_tags = [["-", "-", "X"],
           ["-", "-", "X"],
           ["-", "-", "X"]]

Auch hier bietet sich eine for-Schleife an. Dabei handelt es sich um dieselbe wie die der Überprüfung der Reihen, mit dem Unterschied, dass die Indexe vertauscht werden müssen.

def check_columns(current_tag):
    for i in range(3):
        if (XO_tags[0][i] == current_tag) and (XO_tags[1][i] == current_tag) and (XO_tags[2][i] == current_tag):
            return True
    return False

Diagonalen überprüfen

Um die Diagonalen zu überprüfen müssen einmal die erste Stelle des ersten Arrays, die zweite Stelle des zweiten Arrays und die dritte Stelle des dritten Arrays mit der aktuellen Markierung übereinstimmen sowie einmal die dritte Stelle des ersten Arrays, die zweite Stelle des zweiten Arrays und die erste Stelle des dritten Arrays. Also folgendermaßen:

XO_tags = [["X", "-", "-"],
           ["-", "X", "-"],
           ["-", "-", "X"]]
XO_tags = [["-", "-", "X"],
           ["-", "X", "-"],
           ["X", "-", "-"]]

Der Einfachheit halber wird diese Überprüfung hier “von Hand” durchgeführt. Das heißt, dass die Indexe in diesem Fall von Hand angegeben werden.

def check_diagonals(current_tag):
    if (XO_tags[0][0] == current_tag) and (XO_tags[1][1] == current_tag) and (XO_tags[2][2] == current_tag):
        return True
    elif (XO_tags[0][2] == current_tag) and (XO_tags[1][1] == current_tag) and (XO_tags[2][0] == current_tag):
        return True
    else:
        return False

Gleichstand überprüfen

Wie oben bereits erklärt, tritt ein Gleichstand dann ein, wenn bisher kein Spieler drei Markierungen in einer Reihe setzen konnte, das Spielfeld allerdings gefüllt ist. Dabei wird die Überprüfung mithilfe von zwei ineinander verschachtelten for-Schleifen durchgeführt – ähnlich wie beim Aufsetzen des Spielfelds. Das Spielfeld ist gefüllt, wenn alle Felder ungleich “-” sind.

def check_draw():
    for i in range(3):
        for j in range(3):
            if not ((XO_tags[i][j] == "X") or (XO_tags[i][j] == "O")):
                return False
    return True

Funktionen zusammenfassen

Damit nach einem Knopfdruck nicht vier verschiedene Funktionen aufgerufen werden müssen, werden alle vier Überprüfungen in einer Funktion check_win() zusammengefasst.

def check_win(current_tag):
    if check_rows(current_tag) == False:
        if check_columns(current_tag) == False:
            if check_diagonals(current_tag) == False:
                if check_draw() == False:
                    return False
                elif check_draw() == True:
                    return "draw"
    return True

Erläuterung: Zuerst werden die Reihen überprüft. Wenn keine der Reihen drei gleiche Markierungen enthält, werden die Spalten überprüft. Wenn keine der Spalten drei gleiche Markierungen enthält, werden die zwei Diagonalen überprüft. Wenn auch die Diagonalen keine gleichen Markierungen vorweisen können, wird liegt kein Gewinn vor und es wird False zurückgegeben. Wenn die Überprüfung auf Unentschieden True zurückgibt, wird "draw" zurückgegeben. In jedem anderen Fall, also wenn die Überprüfung der Reihen, Spalten oder Diagonalen True zurückgibt, liegt ein Gewinn vor und es wird True zurückgegeben.

In den Code einsetzen

Nach jedem Klick muss ein möglicher Gewinn überprüft werden, also check_win(current_tag) aufgerufen werden. Zudem wird ein Text win_label eingefügt, wobei der Text je nach return-Wert von check_win verändert wird. Wenn ein Gewinn vorliegt, wird der Spieler angezeigt und wenn ein Unentschieden vorliegt wird dementsprechend “Draw !!” ausgegeben.

import tkinter as tk

window = tk.Tk()
window.resizable(False, False)
window.title("Tic Tac Toe")

game_setup = tk.Frame(window)
XO_tags = [["-", "-", "-"],
           ["-", "-", "-"],
           ["-", "-", "-"]]
current_tag = "X"
win_label = tk.Label(window)

def check_rows(current_tag):
    for i in range(3):
        if (XO_tags[i][0] == current_tag) and (XO_tags[i][1] == current_tag) and (XO_tags[i][2] == current_tag):
            return True
    return False

def check_columns(current_tag):
    for i in range(3):
        if (XO_tags[0][i] == current_tag) and (XO_tags[1][i] == current_tag) and (XO_tags[2][i] == current_tag):
            return True
    return False

def check_diagonals(current_tag):
    if (XO_tags[0][0] == current_tag) and (XO_tags[1][1] == current_tag) and (XO_tags[2][2] == current_tag):
        return True
    elif (XO_tags[0][2] == current_tag) and (XO_tags[1][1] == current_tag) and (XO_tags[2][0] == current_tag):
        return True
    else:
        return False

def check_draw():
    for i in range(3):
        for j in range(3):
            if not ((XO_tags[i][j] == "X") or (XO_tags[i][j] == "O")):
                return False
    return True

def check_win(current_tag):
    if check_rows(current_tag) == False:
        if check_columns(current_tag) == False:
            if check_diagonals(current_tag) == False:
                if check_draw() == False:
                    return False
                elif check_draw() == True:
                    return "draw"
    return True

class XO_tag:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.value = None
        self.button = tk.Button(
            game_setup,
            text = "",
            width = 10,
            height = 5,
            bg = "white",
            fg = "black",
            command = self.set_tag,
            )
        self.button.grid(row = x, column = y, padx = 2, pady = 2)

    def set_tag(self):
        global current_tag
        if self.value == None:
            self.value = current_tag
            XO_tags[self.x][self.y] = current_tag
            if current_tag == "X":
                self.button.configure(text=current_tag, fg = "red")
                new_tag = "O"
            else:
                self.button.configure(text=current_tag, fg = "blue")
                new_tag = "X"
            
        if not check_win(current_tag) == False:
            if check_win(current_tag) == True:
                win_label.configure(text = str(current_tag) + " wins !!")
            else:
                win_label.configure(text = "Draw !!")              
            win_label.pack()

        current_tag = new_tag
        which_player.configure(text = 'Player ' + str(current_tag))

for x in range(3):
    for y in range(3):
        XO_tag(x, y)

which_player = tk.Label(window, text = 'Player ' + str(current_tag))
which_player.pack()
  
game_setup.pack(padx = 5, pady = 5)
window.mainloop()

Die Knöpfe des Spielfelds deaktivieren

Nach einem Gewinn beziehungsweise Unentschieden sollen die Knöpfe des Spielfelds nicht mehr anklickbar sein. Dafür wird allerdings ein neues Array benötigt, in dem sich die Objekte der Knöpfe befinden, sodass über diese iteriert werden kann.

all_XO = []

for x in range(3):
    for y in range(3):
        XO_tag(x, y)
        all_XO.append(XO_tag(x, y))

Wenn nun ein Gewinn oder ein Unentschieden vorliegt, wird über das Array all_XO iteriert und die Einstellungen jedes einzelnen Knopfes bearbeitet.

for button in all_XO:
    button.button.configure(state = "disabled")

Hier im bisherigen Code:

import tkinter as tk

window = tk.Tk()
window.resizable(False, False)
window.title("Tic Tac Toe")

game_setup = tk.Frame(window)
all_XO = []
XO_tags = [["-", "-", "-"],
           ["-", "-", "-"],
           ["-", "-", "-"]]
current_tag = "X"
win_label = tk.Label(window)

def check_rows(current_tag):
    for i in range(3):
        if (XO_tags[i][0] == current_tag) and (XO_tags[i][1] == current_tag) and (XO_tags[i][2] == current_tag):
            return True
    return False

def check_columns(current_tag):
    for i in range(3):
        if (XO_tags[0][i] == current_tag) and (XO_tags[1][i] == current_tag) and (XO_tags[2][i] == current_tag):
            return True
    return False

def check_diagonals(current_tag):
    if (XO_tags[0][0] == current_tag) and (XO_tags[1][1] == current_tag) and (XO_tags[2][2] == current_tag):
        return True
    elif (XO_tags[0][2] == current_tag) and (XO_tags[1][1] == current_tag) and (XO_tags[2][0] == current_tag):
        return True
    else:
        return False

def check_draw():
    for i in range(3):
        for j in range(3):
            if not ((XO_tags[i][j] == "X") or (XO_tags[i][j] == "O")):
                return False
    return True

def check_win(current_tag):
    if check_rows(current_tag) == False:
        if check_columns(current_tag) == False:
            if check_diagonals(current_tag) == False:
                if check_draw() == False:
                    return False
                elif check_draw() == True:
                    return "draw"
    return True

class XO_tag:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.value = None
        self.button = tk.Button(
            game_setup,
            text = "",
            width = 10,
            height = 5,
            bg = "white",
            fg = "black",
            command = self.set_tag,
            )
        self.button.grid(row = x, column = y, padx = 2, pady = 2)

    def set_tag(self):
        global current_tag
        if self.value == None:
            self.value = current_tag
            XO_tags[self.x][self.y] = current_tag
            if current_tag == "X":
                self.button.configure(text=current_tag, fg = "red")
                new_tag = "O"
            else:
                self.button.configure(text=current_tag, fg = "blue")
                new_tag = "X"
            
        if not check_win(current_tag) == False:
            if check_win(current_tag) == True:
                win_label.configure(text = str(current_tag) + " wins !!")
            else:
                win_label.configure(text = "Draw !!")              
            win_label.pack()
            for button in all_XO:
                 button.button.configure(state = "disabled")

        current_tag = new_tag
        which_player.configure(text = 'Player ' + str(current_tag))

for x in range(3):
    for y in range(3):
        XO_tag(x, y)
        all_XO.append(XO_tag(x, y))

which_player = tk.Label(window, text = 'Player ' + str(current_tag))
which_player.pack()
  
game_setup.pack(padx = 5, pady = 5)
window.mainloop()

Revanche-Nachfrage

Nach Spielende soll je nach Wahl das Spielfeld zurückgesetzt werden wenn eine Revanche stattfinden soll, oder nicht. Hierfür wird eine neuer Frame erzeugt, in dem sich ein Label und zwei Button befinden.

request_frame = tk.Frame(window, borderwidth = 2, relief = "sunken")

def rematch():    
    request_label = tk.Label(request_frame, text = "Rematch ??")
    request_label.grid(row = 0, padx = 5, pady = 5)
    button_yes = tk.Button(request_frame, text = "Yes", command = lambda: [request_frame.pack_forget(), reset()])
    button_yes.grid(row = 1, column = 0, padx = (0, 0), pady = 5)
    button_no = tk.Button(request_frame, text = "No", command = lambda: [request_frame.pack_forget()])
    button_no.grid(row = 1, column = 1, padx = (0, 30), pady = 5)
    request_frame.pack(padx = 5, pady = 5)

Wenn der Knopf für eine Revanche gedrückt wird, dann wird der Frame wieder aus dem Fenster genommen und das Spielfeld mit der Funktion reset() zurückgesetzt. Wenn der Knopf für keine Revanche gedrückt wird, wird lediglich der Frame entfernt. Dies geschieht über den Button-Parameter command.

Das Spielfeld zurücksetzen

Such für diese Funktion wird unter anderem das Array all_XO benötigt, um die Einstellungen aller Knöpfe zu verändern. Dabei werden die Knöpfe wieder aktiviert, der Text geleert und der Wert value der Knöpfe zurück auf None gesetzt. Abgesehen davon werden alle Werte des zweidimensionalen Arrays XO_tags wieder mit “-” überschrieben. Zuletzt wird der Text des Gewinns aus dem Fenster entfernt.

def reset():
    for button in all_XO:
        button.button.configure(state = "normal")
        button.button.configure(text="")
        button.value = None

    for i in range(3):
        for j in range(3):
            XO_tags[i][j] = "-"

    win_label.pack_forget()

Revanche-Nachfrage im Spiel

import tkinter as tk

window = tk.Tk()
window.resizable(False, False)
window.title("Tic Tac Toe")

game_setup = tk.Frame(window)
all_XO = []
XO_tags = [["-", "-", "-"],
           ["-", "-", "-"],
           ["-", "-", "-"]]
current_tag = "X"
request_frame = tk.Frame(window, borderwidth = 2, relief = "sunken")
win_label = tk.Label(window)

def check_rows(current_tag):
    for i in range(3):
        if (XO_tags[i][0] == current_tag) and (XO_tags[i][1] == current_tag) and (XO_tags[i][2] == current_tag):
            return True
    return False

def check_columns(current_tag):
    for i in range(3):
        if (XO_tags[0][i] == current_tag) and (XO_tags[1][i] == current_tag) and (XO_tags[2][i] == current_tag):
            return True
    return False

def check_diagonals(current_tag):
    if (XO_tags[0][0] == current_tag) and (XO_tags[1][1] == current_tag) and (XO_tags[2][2] == current_tag):
        return True
    elif (XO_tags[0][2] == current_tag) and (XO_tags[1][1] == current_tag) and (XO_tags[2][0] == current_tag):
        return True
    else:
        return False

def check_draw():
    for i in range(3):
        for j in range(3):
            if not ((XO_tags[i][j] == "X") or (XO_tags[i][j] == "O")):
                return False
    return True

def check_win(current_tag):
    if check_rows(current_tag) == False:
        if check_columns(current_tag) == False:
            if check_diagonals(current_tag) == False:
                if check_draw() == False:
                    return False
                elif check_draw() == True:
                    return "draw"
    return True

class XO_tag:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.value = None
        self.button = tk.Button(
            game_setup,
            text = "",
            width = 10,
            height = 5,
            bg = "white",
            fg = "black",
            command = self.set_tag,
            )
        self.button.grid(row = x, column = y, padx = 2, pady = 2)

    def set_tag(self):
        global current_tag
        if self.value == None:
            self.value = current_tag
            XO_tags[self.x][self.y] = current_tag
            if current_tag == "X":
                self.button.configure(text=current_tag, fg = "red")
                new_tag = "O"
            else:
                self.button.configure(text=current_tag, fg = "blue")
                new_tag = "X"
            
        if not check_win(current_tag) == False:
            if check_win(current_tag) == True:
                win_label.configure(text = str(current_tag) + " wins !!")
            else:
                win_label.configure(text = "Draw !!")              
            win_label.pack()
            for button in all_XO:
                button.button.configure(state = "disabled")
            rematch()

        current_tag = new_tag
        which_player.configure(text = 'Player ' + str(current_tag))

def rematch():    
    request_label = tk.Label(request_frame, text = "Rematch ??")
    request_label.grid(row = 0, padx = 5, pady = 5)
    button_yes = tk.Button(request_frame, text = "Yes", command = lambda: [request_frame.pack_forget(), reset()])
    button_yes.grid(row = 1, column = 0, padx = (0, 0), pady = 5)
    button_no = tk.Button(request_frame, text = "No", command = lambda: [request_frame.pack_forget()])
    button_no.grid(row = 1, column = 1, padx = (0, 30), pady = 5)
    request_frame.pack(padx = 5, pady = 5)

def reset():
    for button in all_XO:
        button.button.configure(state = "normal")
        button.button.configure(text="")
        button.value = None

    for i in range(3):
        for j in range(3):
            XO_tags[i][j] = "-"

    win_label.pack_forget()

for x in range(3):
    for y in range(3):
        XO_tag(x, y)
        all_XO.append(XO_tag(x, y))

which_player = tk.Label(window, text = 'Player ' + str(current_tag))
which_player.pack()
  
game_setup.pack(padx = 5, pady = 5)
window.mainloop()

Styling der Schrift

Um Schriften zu verändern muss eine neue Bibliothek importiert werden.

from tkinter import font

Nun kann eine Schrift folgendermaßen festgelegt werden:

game_font = font.Font(family='Comic Sans MS', size=12, weight='bold')

Hinweis: Je nach ausgewählter Schrift und deren Größe muss eventuell die Größe der Buttons überarbeitet werden. In diesem Fall muss die Höhe height von 5 auf 4 herabgesetzt werden.

Jetzt kann die Variable game_font in die einzelnen Elemente unter dem Parameter font eingesetzt werden.

import tkinter as tk
from tkinter import font

window = tk.Tk()
window.resizable(False, False)
window.title("Tic Tac Toe")

game_setup = tk.Frame(window)
all_XO = []
XO_tags = [["-", "-", "-"],
           ["-", "-", "-"],
           ["-", "-", "-"]]
current_tag = "X"
game_font = font.Font(family='Comic Sans MS', size=12, weight='bold')
request_frame = tk.Frame(window, borderwidth = 2, relief = "sunken")
win_label = tk.Label(window, font = game_font)

def check_rows(current_tag):
    for i in range(3):
        if (XO_tags[i][0] == current_tag) and (XO_tags[i][1] == current_tag) and (XO_tags[i][2] == current_tag):
            return True
    return False

def check_columns(current_tag):
    for i in range(3):
        if (XO_tags[0][i] == current_tag) and (XO_tags[1][i] == current_tag) and (XO_tags[2][i] == current_tag):
            return True
    return False

def check_diagonals(current_tag):
    if (XO_tags[0][0] == current_tag) and (XO_tags[1][1] == current_tag) and (XO_tags[2][2] == current_tag):
        return True
    elif (XO_tags[0][2] == current_tag) and (XO_tags[1][1] == current_tag) and (XO_tags[2][0] == current_tag):
        return True
    else:
        return False

def check_draw():
    for i in range(3):
        for j in range(3):
            if not ((XO_tags[i][j] == "X") or (XO_tags[i][j] == "O")):
                return False
    return True

def check_win(current_tag):
    if check_rows(current_tag) == False:
        if check_columns(current_tag) == False:
            if check_diagonals(current_tag) == False:
                if check_draw() == False:
                    return False
                elif check_draw() == True:
                    return "draw"
    return True

class XO_tag:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.value = None
        self.button = tk.Button(
            game_setup,
            text = "",
            width = 10,
            height = 4,
            bg = "white",
            fg = "black",
            font = game_font,
            command = self.set_tag,
            )
        self.button.grid(row = x, column = y, padx = 2, pady = 2)

    def set_tag(self):
        global current_tag
        if self.value == None:
            self.value = current_tag
            XO_tags[self.x][self.y] = current_tag
            if current_tag == "X":
                self.button.configure(text=current_tag, fg = "red")
                new_tag = "O"
            else:
                self.button.configure(text=current_tag, fg = "blue")
                new_tag = "X"
            
        if not check_win(current_tag) == False:
            if check_win(current_tag) == True:
                win_label.configure(text = str(current_tag) + " wins !!")
            else:
                win_label.configure(text = "Draw !!")              
            win_label.pack()
            for button in all_XO:
                button.button.configure(state = "disabled")
            rematch()

        current_tag = new_tag
        which_player.configure(text = 'Player ' + str(current_tag))

def rematch():    
    request_label = tk.Label(request_frame, text = "Rematch ??", font = game_font)
    request_label.grid(row = 0, padx = 5, pady = 5)
    button_yes = tk.Button(request_frame, text = "Yes", font = game_font, command = lambda: [request_frame.pack_forget(), reset()])
    button_yes.grid(row = 1, column = 0, padx = (0, 0), pady = 5)
    button_no = tk.Button(request_frame, text = "No", font = game_font, command = lambda: [request_frame.pack_forget()])
    button_no.grid(row = 1, column = 1, padx = (0, 30), pady = 5)
    request_frame.pack(padx = 5, pady = 5)

def reset():
    for button in all_XO:
        button.button.configure(state = "normal")
        button.button.configure(text="")
        button.value = None

    for i in range(3):
        for j in range(3):
            XO_tags[i][j] = "-"

    win_label.pack_forget()

for x in range(3):
    for y in range(3):
        XO_tag(x, y)
        all_XO.append(XO_tag(x, y))

which_player = tk.Label(window, text = 'Player ' + str(current_tag), font = game_font)
which_player.pack()
  
game_setup.pack(padx = 5, pady = 5)
window.mainloop()

Der vollständige Code

import tkinter as tk
from tkinter import font

window = tk.Tk()
window.resizable(False, False)
window.title("Tic Tac Toe")

game_setup = tk.Frame(window)
all_XO = []
XO_tags = [["-", "-", "-"],
           ["-", "-", "-"],
           ["-", "-", "-"]]
current_tag = "X"
game_font = font.Font(family='Comic Sans MS', size=12, weight='bold')
request_frame = tk.Frame(window, borderwidth = 2, relief = "sunken")
win_label = tk.Label(window, font = game_font)

def check_rows(current_tag):
    for i in range(3):
        if (XO_tags[i][0] == current_tag) and (XO_tags[i][1] == current_tag) and (XO_tags[i][2] == current_tag):
            return True
    return False

def check_columns(current_tag):
    for i in range(3):
        if (XO_tags[0][i] == current_tag) and (XO_tags[1][i] == current_tag) and (XO_tags[2][i] == current_tag):
            return True
    return False

def check_diagonals(current_tag):
    if (XO_tags[0][0] == current_tag) and (XO_tags[1][1] == current_tag) and (XO_tags[2][2] == current_tag):
        return True
    elif (XO_tags[0][2] == current_tag) and (XO_tags[1][1] == current_tag) and (XO_tags[2][0] == current_tag):
        return True
    else:
        return False

def check_draw():
    for i in range(3):
        for j in range(3):
            if not ((XO_tags[i][j] == "X") or (XO_tags[i][j] == "O")):
                return False
    return True

def check_win(current_tag):
    if check_rows(current_tag) == False:
        if check_columns(current_tag) == False:
            if check_diagonals(current_tag) == False:
                if check_draw() == False:
                    return False
                elif check_draw() == True:
                    return "draw"
    return True

class XO_tag:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.value = None
        self.button = tk.Button(
            game_setup,
            text = "",
            width = 10,
            height = 4,
            bg = "white",
            fg = "black",
            font = game_font,
            command = self.set_tag,
            )
        self.button.grid(row = x, column = y, padx = 2, pady = 2)

    def set_tag(self):
        global current_tag
        if self.value == None:
            self.value = current_tag
            XO_tags[self.x][self.y] = current_tag
            if current_tag == "X":
                self.button.configure(text=current_tag, fg = "red")
                new_tag = "O"
            else:
                self.button.configure(text=current_tag, fg = "blue")
                new_tag = "X"
            
        if not check_win(current_tag) == False:
            if check_win(current_tag) == True:
                win_label.configure(text = str(current_tag) + " wins !!")
            else:
                win_label.configure(text = "Draw !!")              
            win_label.pack()
            for button in all_XO:
                button.button.configure(state = "disabled")
            rematch()

        current_tag = new_tag
        which_player.configure(text = 'Player ' + str(current_tag))

def rematch():    
    request_label = tk.Label(request_frame, text = "Rematch ??", font = game_font)
    request_label.grid(row = 0, padx = 5, pady = 5)
    button_yes = tk.Button(request_frame, text = "Yes", font = game_font, command = lambda: [request_frame.pack_forget(), reset()])
    button_yes.grid(row = 1, column = 0, padx = (0, 0), pady = 5)
    button_no = tk.Button(request_frame, text = "No", font = game_font, command = lambda: [request_frame.pack_forget()])
    button_no.grid(row = 1, column = 1, padx = (0, 30), pady = 5)
    request_frame.pack(padx = 5, pady = 5)

def reset():
    for button in all_XO:
        button.button.configure(state = "normal")
        button.button.configure(text="")
        button.value = None

    for i in range(3):
        for j in range(3):
            XO_tags[i][j] = "-"

    win_label.pack_forget()

for x in range(3):
    for y in range(3):
        XO_tag(x, y)
        all_XO.append(XO_tag(x, y))

which_player = tk.Label(window, text = 'Player ' + str(current_tag), font = game_font)
which_player.pack()
  
game_setup.pack(padx = 5, pady = 5)
window.mainloop()
18820cookie-checkTic Tac Toe-Spiel in Python

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *