简体   繁体   中英

Why is my function not affecting a button in tkinter?

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:

  • Learn how to create and use classes, so you can separate and organize your code into meaningful chunks.
  • Your approach is to manually define each individual thing, even if they are the exact same thing. You should consider a more dynamic approach where you create something once and let a loop create and position a bunch of copies/instances of it.
  • If your code was written properly, according to the design that you laid out, you would technically have a Button that recreates itself when it is clicked. This type of circular logic is bad.
  • Worry less about 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM