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()
One Comment
Pingback: