I'm trying to make Tic Tac Toe in python using tkinter, but I'm running into an issue. I'm trying to make it so when you click on one of the squares, it displays whichever player's symbol on that square. However, with my current function playerCheck
, nothing happens when I press them. I can't figure out why this is happening, so I would appreciate any help.
Keep in mind I am in no way finished.
import tkinter as tk
Player1 = "X"
Player2 = "O"
turn = None
playerNumber = None
def playerCheck(function):
if turn == Player1:
playerNumber = Player1
function("white", playerNumber)
elif turn == Player2:
playerNumber = Player2
function("white", playerNumber)
def topLeft(color, player):
topL = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(topL)).grid(row=0,column=0)
def topMiddle(color, player):
topM = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(topMiddle)).grid(row=0,column=1)
def topRight(color, player):
topR = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(topRight)).grid(row=0,column=2)
def middleLeft(color, player):
midL = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(middleLeft)).grid(row=1,column=0)
def middleMiddle(color, player):
midM = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(middleMiddle)).grid(row=1,column=1)
def middleRight(color, player):
midR = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(middleRight)).grid(row=1,column=2)
def bottomLeft(color, player):
botL = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(bottomLeft)).grid(row=2,column=0)
def bottomMiddle(color, player):
botM = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(bottomMiddle)).grid(row=2,column=1)
def bottomRight(color, player):
botR = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(bottomRight)).grid(row=2,column=2)
def gameStart():
topLeft("white", "")
topMiddle("white", "")
topRight("white", "")
middleLeft("white", "")
middleMiddle("white", "")
middleRight("white", "")
bottomLeft("white", "")
bottomMiddle("white", "")
bottomRight("white", "")
turn = Player1
def Main():
a = tk.Tk()
a.title("Tick Tack Toe")
a.geometry("250x250")
gameStart()
a.mainloop()
Main()
The argument to playerCheck()
is a button, not a function. You should change the button's text and color, not try to call it.
def playerCheck(button):
global turn
button['text'] = turn
button['fg'] = "white"
if turn == Player1:
turn = Player2
else:
turn = Player1
Here is an example of a completely working tic-tac-toe made with tkinter
. I used your Button
approach. If you are going to make anything with tkinter
it should look a lot more like the below code. I commented it all over the place to give you a bit of a guide to what everything is doing, but I did not dumb down the code. Some parts may be very hard for you to understand (like the logic in win_or_block()
). That's a good thing. It will force you to research and get better.
tips:
Button
that recreates itself when it is clicked. This type of circular logic is bad.Buttons
and more about game logic. Your graphics should simply express a condition. You're on your way to making the graphics serve as the logic behind your conditions, and that is a terrible approach.#python 3.8+
from tkinter import Tk, Frame, Button, Entry, StringVar, IntVar, Checkbutton
from statistics import mean
from random import choice
class Splash(Frame):
WIDTH = 300
HEIGHT = 190
def __init__(self, master, callback, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.callback = callback
self.message = StringVar()
self.tricky = IntVar()
#just to make some lines shorter
e = dict(font='calibri 18 bold', bg=self['bg'], border=0, justify='center', textvariable=self.message)
b = dict(font='calibri 18 bold', bg='black', fg='white')
c = dict(font='calibri 16 bold', bg=self['bg'])
Entry(self, **e).place(width=280, height=40, x=10, y=5)
Checkbutton(self, text="tricky", variable=self.tricky, **c).place(width=200, height=30, x=50, y=50)
Button(self, text='1 Player' , command=lambda: self.remove(True) , **b).place(width=200, height=45, x=50, y=90)
Button(self, text='2 Players', command=lambda: self.remove(False), **b).place(width=200, height=45, x=50, y=140)
def remove(self, auto):
self.place_forget()
self.callback(auto)
class Board(Frame):
#if you change this you need to change the font size in self.default_cell and vise-versa
WIDTH = 450
HEIGHT = 393
def __init__(self, master, callback, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
#cell default properties
self.default_cell = dict(
foreground = 'black',
background = 'black',
activebackground = 'black',
disabledforeground = 'black',
text = "",
font = "consolas 48 bold",
relief = 'sunken',
state = 'normal',
width = 4,
height = 1,
)
#make board
for i in range(9):
cell = Button(self, **self.default_cell)
cell.config(command=lambda c=cell, i=i: callback(c, i))
cell.grid(row=int(i//3), column=i%3, sticky='nswe')
def reset(self):
for cell in self.winfo_children():
cell.config(**self.default_cell)
#used to stop cell clicks while splash screen is visible
def lock(self):
for cell in self.winfo_children():
cell.config(state='disabled')
class Game(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
#current player
self.player = 0
#True: against computer - False: against 2nd human
self.auto = False
#stores moves
self.moves = [0]*9
#player colors
self.color = ['red', 'green']
# [crossed swords, flag outline]
self.marks = [chr(9876), chr(9872)]
#positions
self.edge = {1, 7, 3, 5}
self.corner = {0, 6, 2, 8}
#True = hard | False = easy
self.tricky = True
'''
numbers that can't average together with 0 to equal each other
where [n, n, n] is any given winning combination in tic-tac-toe
consider the mean of: [1, 2, 0] vs [1, 1, 1]
against the mean of: [1, 4, 0] vs [1, 1, 1]
'''
self.pids = [1, 4]
#init board
self.board = Board(self, self.move)
self.board.grid(row=0, column=0, sticky='nswe')
#init "game over" screen and place() properties
self.splash = Splash(self, callback=self.reset, bg='gray92')
self.splashprops = dict(
x = (Board.WIDTH-Splash.WIDTH)/2,
y = (Board.HEIGHT-Splash.HEIGHT)/2,
width = Splash.WIDTH,
height = Splash.HEIGHT
)
self.show_splash("Connect 3 And Conquer")
'''
This method is tricky. It is used to check:
1: if the current player has won (id=current_id, n=4)
2: if the computer can win (id=player2_id, n=3)
3: if the computer can block a win (id=player1_id, n=3)
'''
def win_or_block(self, id, n=3):
best = -1
try:
for a in range(3):
#vertical
m = self.moves[a::3]
l = list(filter(lambda i: i>0, m))+[id]
if len(l) == n and mean(l) == id:
best = (m.index(0) * 3) + a
break
#horizontal
m = self.moves[a*3:a*3+3]
l = list(filter(lambda i: i>0, m))+[id]
if len(l) == n and mean(l) == id:
best = m.index(0) + (a * 3)
break
#back slash
if best < 0:
m = self.moves[0::4]
l = list(filter(lambda i: i>0, m))+[id]
if len(l) == n and mean(l) == id:
best = m.index(0) * 4
#forward slash
if best < 0:
m = self.moves[2::2][0:3]
l = list(filter(lambda i: i>0, m))+[id]
if len(l) == n and mean(l) == id:
best = (m.index(0) + 1) * 2
except ValueError:
#m.index(0) does not exist, current player has won
best = 0
return best
def best(self, index):
best = -1
#computer checks if it can win, and if not, then if it can block
for i in range(1, -1, -1):
best = self.win_or_block(self.pids[i])
if best > -1:
break
#if the computer cannot win or there is nothing to block
if best < 0:
avail = {i for i, v in enumerate(self.moves) if v == 0}
c = list(self.corner.intersection(avail)) #corners
e = list(self.edge.intersection(avail)) #edges
#choose the middle or mirror opposite of the last move
if self.tricky:
b = 4 if 4 in avail else abs(index - 8)
if b in avail:
best = b
#attempt a random choice in this order: corner, center, edge
if best < 0:
if len(c):
best = choice(c)
elif 4 in avail:
best = 4
else:
best = choice(e)
return best
def move(self, button, index):
#mark the square
button.config(bg=self.color[self.player], text=self.marks[self.player], state='disabled')
#record the move
self.moves[index] = self.pids[self.player]
#game over screens
if self.win_or_block(self.pids[self.player], 4) > -1: #check win
self.show_splash(f'Player {self.player+1} Has Conquered!')
return
elif self.moves.count(0) == 0: #check "no more moves"
self.show_splash('Stalemate!')
return
#derive next player ~ Player1 = 0, Player2 = 1
self.player = (self.player + 1)%2
if self.auto and self.player == 1:
#invoke the button with the child index that corresponds to the "best" move
self.board.winfo_children()[self.best(index)].invoke()
def show_splash(self, msg):
self.board.lock()
self.splash.message.set(msg)
self.splash.place(**self.splashprops)
def reset(self, auto):
self.tricky = bool(self.splash.tricky.get())
self.auto = auto
self.player = 0
self.moves = [0]*9
self.board.reset()
if __name__ == '__main__':
app = Game()
app.title("Connect 3 And Conquer")
#lock down the window size
app.minsize(Board.WIDTH, Board.HEIGHT)
app.maxsize(Board.WIDTH, Board.HEIGHT)
app.mainloop()
aside:
I understand you are new. Don't take my criticisms to heart. The point is to make you better. Anybody could come in here and tell you how to make poor code work. My agenda is to stop you from writing poor code in the first place - by giving you an excellent example and explaining where you went wrong.
Cheers
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.