Allgemein,  Programmieren

Upgrade: Neue Features für das Tic Tac Toe-Spiel

Um diese Änderungen vorzunehmen wird der Code aus dem Beitrag “Tic Tac Toe-Spiel in Python” modifiziert, also wird nicht der gesamte Code detailliert besprochen.

Variable Raster-Größe

Gewinnmöglichkeiten checken variablenabhängig machen

Damit die Überprüfung eines Gewinns bei jeder Größe des Rasters funktioniert, müssen die Funktion unabhängig von einer Zahl, dafür aber anhängig von einer Variable grid_size gemacht werden.

Reihen prüfen

Hier muss im alten Code lediglich jede 3 gegen die Variable grid_size ersetzt werden. Außerdem wird eine Variable check eingeführt, die anfangs den Positiv-Zustand True annimmt und ihren Status zu False ändert, sobald ein Eintrag eines Buttons ungleich der aktuellen Markierung ist. Außerdem wird die Überprüfung unterbrochen, wenn alle drei Markierungen einer Reihe gleich sind, während True für einen Gewinn zurückgegeben wird.

Alter Code

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

Neuer Code

def check_rows(current_tag):
    for i in range(grid_size):
        check = True
        for j in range(grid_size):
            if not (XO_tags[i][j] == current_tag):
                check = False

        if check == True:
            return True   
    return False

Spalten prüfen

Auf hier werden alle 3er mit grid_size ersetzt und eine Variable check eingeführt.

Alter Code

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

Neuer Code

def check_columns(current_tag):
    for i in range(grid_size):
        check = True
        for j in range(grid_size):
            if not (XO_tags[j][i] == current_tag):
                check = False

        if check == True:
            return True
    return False

Diagonalen prüfen

Diese Funktion muss noch einmal neu durchdacht werden. Erstmal hier der alte Code:

Alter Code

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

Es müssen bei Tic Tac Toe ja bekanntlich 2 Diagonalen überprüft werden. Diese sehen beispielhaft wie folgt aus:

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

Die erste Diagonale (links) besteht aus den Koordinaten [0][0], [1][1] und [2][2]. Daher kann sie mit einer einfachen for-Schleife überprüft werden. Außerdem wird wieder eine Variable check eingeführt, mit deren Hilfe die Schleife abgebrochen wird, sobald ein Feld der Diagonale eine andere Markierung hat.

def check_diagonals(current_tag):
    check = True
    for i in range(grid_size):
        if not (XO_tags[i][i] == current_tag):
            check = False
            continue

Wenn check nun immer noch True ist, liegt bereits ein Gewinn vor und die andere Diagonale muss nicht mehr überprüft werden.

    if check == True:
        return True

Anderenfalls muss die zweite Diagonale (rechts) noch überprüft werden, wobei auch hier check anfangs True ist. Diese besteht aus den Koordinaten [0][2], [1][1] und [2][0]. Das heißt, dass innerhalb der for-Schleife, die hochzählt, ein zweiter Zähler counter benötigt wird, der von grid_size-1 runterzählt. Die -1 kommt daher, dass ein Array beim Index 0, nicht bei 1, anfängt zu zählen.

    else:
        check = True
        counter = grid_size -1
        for j in range(grid_size):
            if not (XO_tags[j][counter] == current_tag):
                check = False
            counter -= 1

Wenn anschließend check noch immer True ist, dann liegt bei der zweiten Diagonale ein Gewinn vor und es wird True zurückgegeben. Anderenfalls gibt es keinen Gewinn und es wird False zurückgegeben.

Neuer Code

def check_diagonals(current_tag):
    check = True
    for i in range(grid_size):
        if not (XO_tags[i][i] == current_tag):
            check = False
            continue
            
    if check == True:
        return True
    
    else:
        check = True
        counter = grid_size -1
        for j in range(grid_size):
            if not (XO_tags[j][counter] == current_tag):
                check = False
            counter -= 1

        if check == True:
            return True
        else:
            return False

Unentschieden prüfen

Hier muss lediglich geprüft werden, ob in allen Feldern des Arrays ein X oder ein O steht. Das heißt, dass auch hier jede 3 in den for-Schleifen durch grid_size ersetzt werden muss.

Alter Code

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

Neuer Code

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

Array XO_tags variablen abhängig machen

Das zweidimensionale Array XO_tags wird zur Überprüfung der Gewinnmöglichkeiten verwendet. Da nun aber die Spielfeldgröße variablenabhängig ist, kann das Array nicht mehr statisch initialisiert werden. Daher wird ein Hilfsarray array initialisiert, das grid_size viele Felder enthält und grid_size oft an XO_tags angehängt wird.

Alter Code

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

Neuer Code

global grid_size
for x in range(grid_size):
    array = []
    for y in range(grid_size):
        XO_tag(x, y)
        all_XO.append(XO_tag(x, y))
        array.append("-")
    XO_tags.append(array)

Größenauswahl mit Spinbox zu Beginn des Spiels

Setup-Funktion

Das Setup des Spiels muss nach der Abfrage nach der Spielfeldgröße erfolgen, da dann die Variable grid_size den ausgewählten Wert hat. Deswegen wird das Setup in eine getrennte Funktion gesetzt.

Alter Code

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

which_player.pack()
game_setup.pack(padx = 5, pady = 5)

Neuer Code

def setup():
    global grid_size
    grid_size = int(select_grid_size.get())
    for x in range(grid_size):
        array = []
        for y in range(grid_size):
            XO_tag(x, y)
            all_XO.append(XO_tag(x, y))
            array.append("-")
        XO_tags.append(array)

    which_player.pack()
    game_setup.pack(padx = 5, pady = 5)

Spinbox und Play-Button einsetzen

Alle Einstellungsmöglichkeiten befinden sich in dem Frame settings. Zuerst wird ein Label select_label erstellt, das dem Spieler sagt, dass er eine Spielfeldgröße festlegen soll. Die Spinbox select_grid_size erstreckt sich von 3 bis 5, wobei standardmäßig der from_-Wert als Ausgangswert in die Spinbox geschrieben wird. Der Play-Button play_button dient dazu, dass der zu diesem Zeitpunkt ausgewählte Wert festgelegt wird.

settings = tk.Frame(window)

select_label = tk.Label(settings, text = "Select grid size", font = game_font)
select_label.grid()

select_grid_size = tk.Spinbox(settings, from_ = 3, to = 5, font = game_font)
select_grid_size.grid(padx = 5, pady = 5)

play_button = tk.Button(settings, text = "Play", font = game_font, command = lambda:[settings.pack_forget(), setup()])
play_button.grid(pady = 5)

settings.pack()

Abfrage in setup()

Für den Aufbau des Spielfeldes muss die Größe, also der Inhalt der Spinbox bekannt sein. Dieser wird folgendermaßen abgefragt:

grid_size = int(select_grid_size.get())

Einsetzen in den Code der Setup-Funktion:

def setup():
    global grid_size
    grid_size = int(select_grid_size.get())
    for x in range(grid_size):
        array = []
        for y in range(grid_size):
            XO_tag(x, y)
            all_XO.append(XO_tag(x, y))
            array.append("-")
        XO_tags.append(array)

    which_player.pack()
    game_setup.pack(padx = 5, pady = 5)

Farbauswahl für X und O

Colorpicker

Für die Farbauswahl wird je eine Combobox für die Spieler X und O erstellt. Um eine Combobox einfügen zu können, muss ttk von tkinter importiert werden.

from tkinter import ttk

Eine Combobox wird zum Beispiel folgendermaßen erstellt:

combobox_X = ttk.Combobox(color_picker, state = "readonly", textvariable = current_color_X, font = game_font, width = 10)

Der Status state = "readonly" legt fest, dass keine neuen Werte in die Combobox eingetragen, sondern nur Werte aus der Liste ausgewählt werden können. Das Attribut textvariable legt fest, welcher Wert als Standard bereits in der Combobox eingetragen steht.

Die Comboboxen werden in einem Frame color_picker zusammengefasst. Ein Array color_array fasst alle Farben, die wählbar sein sollen sein sollen. Innerhalb des Rasters im Frame werden in der ersten Spalte ein Label und eine Combobox für Spieler X und in der zweiten Spalte für Spieler O eingefügt.

color_picker = tk.Frame(settings)

color_array = ["red", "orange", "green", "blue", "purple", "grey"]

color_label_X = tk.Label(color_picker, text = "Player X", font = game_font)
color_label_X.grid(column = 0, row = 0)
current_color_X = tk.StringVar(value = "red")
combobox_X = ttk.Combobox(color_picker, state = "readonly", textvariable = current_color_X, font = game_font, width = 10)
combobox_X["values"] = color_array
combobox_X.grid(column = 0, row = 1, padx = 5)

color_label_O = tk.Label(color_picker, text = "Player O", font = game_font)
color_label_O.grid(column = 1, row = 0)
current_color_O = tk.StringVar(value = "blue")
combobox_O = ttk.Combobox(color_picker, state = "readonly", textvariable = current_color_O, font = game_font, width = 10)
combobox_O["values"] = color_array
combobox_O.grid(column = 1, row = 1, padx = 5)  

color_picker.grid()

Einstellungen überprüfen

Zur Überprüfung der Einstellungen wird eine neue Funktion settings erstellt, die prüfen soll, ob zwei gleiche Farben ausgewählt wurden. Außerdem wird ein Hinweis ausgegeben wenn zwei gleiche Farben ausgesucht wurden. Wenn dies nicht der Fall ist, wird die Setup-Funktion ausgeführt und der settings-Frame entfernt.

def check_settings():
    if(combobox_X.get() == combobox_O.get()):
        warning = tk.Label(settings, text = "Please choose different colors", font = game_font, fg = "red")
        warning.grid()
        return

    setup()
    settings.pack_forget()

Die Funktion, die nach dem Drücken des Play-Buttons ausgeführt werden soll muss geändert werden.

Vorher

play_button = tk.Button(settings, text = "Play", font = game_font, command = lambda:[settings.pack_forget(), setup()])

Nachher

play_button = tk.Button(settings, text = "Play", font = game_font, command = check_settings)

Farbabfrage beim Setzen einer Markierung

Eine Combobox wird zum Beispiel folgendermaßen abgefragt:

combobox_X.get()

Diese Zeile wird als Schriftfarbe fg des jeweiligen Button eingesetzt.

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 = combobox_X.get())
            new_tag = "O"
        else:
            self.button.configure(text=current_tag, fg = combobox_O.get())
            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))

Der vollständige Code

import tkinter as tk
from tkinter import ttk
from tkinter import font

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

all_XO = []
XO_tags = []
current_tag = "X"

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

game_setup = tk.Frame(window)
which_player = tk.Label(window, text = 'Player ' + str(current_tag), font = game_font)

def check_settings():
    if(combobox_X.get() == combobox_O.get()):
        warning = tk.Label(settings, text = "Please choose different colors", font = game_font, fg = "red")
        warning.grid()
        return

    setup()
    settings.pack_forget()

def setup():
    global grid_size
    grid_size = int(select_grid_size.get())
    for x in range(grid_size):
        array = []
        for y in range(grid_size):
            XO_tag(x, y)
            all_XO.append(XO_tag(x, y))
            array.append("-")
        XO_tags.append(array)

    which_player.pack()
    game_setup.pack(padx = 5, pady = 5)

def check_rows(current_tag):
    for i in range(grid_size):
        check = True
        for j in range(grid_size):
            if not (XO_tags[i][j] == current_tag):
                check = False

        if check == True:
            return True   
    return False

def check_columns(current_tag):
    for i in range(grid_size):
        check = True
        for j in range(grid_size):
            if not (XO_tags[j][i] == current_tag):
                check = False

        if check == True:
            return True
    return False

def check_diagonals(current_tag):
    check = True
    for i in range(grid_size):
        if not (XO_tags[i][i] == current_tag):
            check = False
            continue
            
    if check == True:
        return True
    
    else:
        check = True
        counter = grid_size -1
        for j in range(grid_size):
            if not (XO_tags[j][counter] == current_tag):
                check = False
            counter -= 1

        if check == True:
            return True
        else:
            return False     

def check_draw():
    for i in range(grid_size):
        for j in range(grid_size):
            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 = combobox_X.get())
                new_tag = "O"
            else:
                self.button.configure(text=current_tag, fg = combobox_O.get())
                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(grid_size):
        for j in range(grid_size):
            XO_tags[i][j] = "-"

    win_label.pack_forget()

settings = tk.Frame(window)

color_picker = tk.Frame(settings)

color_array = ["red", "orange", "green", "blue", "purple", "grey"]

color_label_X = tk.Label(color_picker, text = "Player X", font = game_font)
color_label_X.grid(column = 0, row = 0)
current_color_X = tk.StringVar(value = "red")
combobox_X = ttk.Combobox(color_picker, state = "readonly", textvariable = current_color_X, font = game_font, width = 10)
combobox_X["values"] = color_array
combobox_X.grid(column = 0, row = 1, padx = 5)

color_label_O = tk.Label(color_picker, text = "Player O", font = game_font)
color_label_O.grid(column = 1, row = 0)
current_color_O = tk.StringVar(value = "blue")
combobox_O = ttk.Combobox(color_picker, state = "readonly", textvariable = current_color_O, font = game_font, width = 10)
combobox_O["values"] = color_array
combobox_O.grid(column = 1, row = 1, padx = 5)  

color_picker.grid()

select_label = tk.Label(settings, text = "Select grid size", font = game_font)
select_label.grid()

select_grid_size = tk.Spinbox(settings, from_ = 3, to = 5, font = game_font)
select_grid_size.grid(padx = 5, pady = 5)

play_button = tk.Button(settings, text = "Play", font = game_font, command = check_settings)
play_button.grid(pady = 5)

settings.pack()

request_frame = tk.Frame(window, borderwidth = 2, relief = "sunken")
win_label = tk.Label(window, font = game_font)

window.mainloop()
19290cookie-checkUpgrade: Neue Features für das Tic Tac Toe-Spiel

One Comment

Leave a Reply

Your email address will not be published.