1

I was writing a Tic-Tac-Toe game using Tkinter. When one user wins, a Toplevel window appears with Restart button, that must restart the program, but I receive unexpected error when I click on it. I know my winner checking function is stupid, but I could write a better one with my current knowledge level. The error message:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\Eldiiar Raiymkulov\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1699, in __call__
    return self.func(*args)
  File "C:\Users\Eldiiar Raiymkulov\Desktop\xo2.py", line 62, in <lambda>
    command=lambda x=x, y=y: clicked(y, x)))
  File "C:\Users\Eldiiar Raiymkulov\Desktop\xo2.py", line 15, in clicked
    isWinner(char)
  File "C:\Users\Eldiiar Raiymkulov\Desktop\xo2.py", line 46, in isWinner
    topMessage(char)
  File "C:\Users\Eldiiar Raiymkulov\Desktop\xo2.py", line 30, in topMessage
    topButton = Button(top, text="Restart", command=restart(root))
NameError: name 'root' is not defined

And here is the code:

from tkinter import *

turn = True
btns = None
def clicked(y, x):
    global turn, btns
    char = ""
    if turn:
        char = "X"
    else:
        char = "O"

    btns[y][x].config(text=char,
                      state=DISABLED)
    isWinner(char)
    turn = not turn

def restart(root):
    global turn
    turn = True
    root.destroy()
    main()
def topMessage(char):
    global root
    top = Toplevel()
    top.title("Congratulations!")
    topText = Label(top, text=f"{char} is a winner!")
    topButton = Button(top, text="Restart", command=restart(root))
    topText.grid(row=0)
    topButton.grid(row=1)
def isWinner(char):
    global root
        #horizontal
    if (((btns[1][1].cget("text") == char) and (btns[1][2].cget("text") == char) and (btns[1][3].cget("text") == char)) or
    ((btns[2][1].cget("text") == char) and (btns[2][2].cget("text") == char) and (btns[2][3].cget("text") == char)) or  
    ((btns[3][1].cget("text") == char) and (btns[3][2].cget("text") == char) and (btns[3][3].cget("text") == char)) or  
        #vertical
    ((btns[1][1].cget("text") == char) and (btns[2][1].cget("text") == char) and (btns[3][1].cget("text") == char)) or
    ((btns[1][2].cget("text") == char) and (btns[2][2].cget("text") == char) and (btns[3][2].cget("text") == char)) or
    ((btns[1][3].cget("text") == char) and (btns[2][3].cget("text") == char) and (btns[3][3].cget("text") == char)) or
        #diagonal
    ((btns[1][1].cget("text") == char) and (btns[2][2].cget("text") == char) and (btns[3][3].cget("text") == char)) or
    ((btns[1][3].cget("text") == char) and (btns[2][2].cget("text") == char) and (btns[3][1].cget("text") == char))):
        topMessage(char)


def main():
    global btns
    root = Tk()
    root.title("X and O of Daniar")
    root.resizable(False, False)
    btns = [None]
    for y in range(1, 4):
        row = [None]
        for x in range(1, 4):
            row.append(Button(root,
                              width=5,
                              height=3,
                              font="time 12 bold",
                              command=lambda x=x, y=y: clicked(y, x)))
            row[x].grid(row=y, column=x)
        btns.append(row)

    Button(root,
           text="Restart",
           width=5,
           command=lambda: restart(root)).grid(row=4, column=2)

    root.mainloop()

main()

Also, if you have any smarter suggestions about isWinner functions, could you please share them with me?

DaniiarR
  • 319
  • 1
  • 4
  • 12

1 Answers1

1

This should fix your issue.

You needed to have a variable defined outside the functions to use it with global, and have main() use that variable. Also if you are calling any function in command and passing a variable to the function, always use lambda.

from tkinter import *

turn = True
btns = None
root = None


def clicked(y, x):
    global turn, btns
    char = ""
    if turn:
        char = "X"
    else:
        char = "O"

    btns[y][x].config(text=char,
                      state=DISABLED)
    isWinner(char)
    turn = not turn


def restart(root):
    global turn
    turn = True
    root.destroy()
    main()


def topMessage(char):
    global root
    top = Toplevel()
    top.title("Congratulations!")
    topText = Label(top, text=f"{char} is a winner!")
    topButton = Button(top, text="Restart", command=lambda: restart(root))
    topText.grid(row=0)
    topButton.grid(row=1)


def isWinner(char):
    global root
    # horizontal
    if (((btns[1][1].cget("text") == char) and (btns[1][2].cget("text") == char) and (btns[1][3].cget("text") == char)) or
        ((btns[2][1].cget("text") == char) and (btns[2][2].cget("text") == char) and (btns[2][3].cget("text") == char)) or
        ((btns[3][1].cget("text") == char) and (btns[3][2].cget("text") == char) and (btns[3][3].cget("text") == char)) or
        # vertical
        ((btns[1][1].cget("text") == char) and (btns[2][1].cget("text") == char) and (btns[3][1].cget("text") == char)) or
        ((btns[1][2].cget("text") == char) and (btns[2][2].cget("text") == char) and (btns[3][2].cget("text") == char)) or
        ((btns[1][3].cget("text") == char) and (btns[2][3].cget("text") == char) and (btns[3][3].cget("text") == char)) or
        # diagonal
        ((btns[1][1].cget("text") == char) and (btns[2][2].cget("text") == char) and (btns[3][3].cget("text") == char)) or
            ((btns[1][3].cget("text") == char) and (btns[2][2].cget("text") == char) and (btns[3][1].cget("text") == char))):
        topMessage(char)


def main():
    global btns
    global root
    root = Tk()
    root.title("X and O of Daniar")
    root.resizable(False, False)
    btns = [None]
    for y in range(1, 4):
        row = [None]
        for x in range(1, 4):
            row.append(Button(root,
                              width=5,
                              height=3,
                              font="time 12 bold",
                              command=lambda x=x, y=y: clicked(y, x)))
            row[x].grid(row=y, column=x)
        btns.append(row)

    Button(root,
           text="Restart",
           width=5,
           command=lambda: restart(root)).grid(row=4, column=2)

    root.mainloop()


main()
Vinay Bharadhwaj
  • 165
  • 1
  • 17
  • Thank you very much, it works now! But why do we always have to use lambda when calling function in command? – DaniiarR Dec 08 '18 at 17:48
  • Maybe this will answere your question, and why lambda is the best way to implement this functionality https://stackoverflow.com/a/47996979/8341461 – Vinay Bharadhwaj Dec 08 '18 at 18:23
  • @DaniiarR One day, you would want to use OOP so that you don't go into these troubles. [Here](https://github.com/Miraj50/Awesome-Tkinter-Apps/tree/master/Tic%20Tac%20Toe) is an example. – Miraj50 Dec 08 '18 at 19:08