Changing Variables within a Function in Tkinter

2019-09-21 03:05发布

问题:

I want the X and O animations to switch back and forth upon mouse clicks. The problem is in the function XorO. I don't really understand why, but it will only create Xs when I click it. I think it may have to do with how I wrote the turn variable. Here is what I have.

from tkinter import *


tk = Tk()
width = 600
third = width / 3
canvas = Canvas(width=width, height=width)
tk.title = ("Tic Tac Toe")


line1 = canvas.create_line(200, 0, 200, 600)
line2 = canvas.create_line(400, 0, 400, 600)
line3 = canvas.create_line(0, 200, 600, 200)
line4 = canvas.create_line(0, 400, 600, 400)



def mouse_click(event):
    col = int(event.x / third)
    row = int(event.y / third)
    XorO(row, col)

def XorO(row,col):
    class XsorOs:
        turn = 1
        if turn is (1 or 3 or 5 or 7 or 9):
            canvas.create_line(col * third, row * third, (col + 1) * third, (row + 1) * third)
            canvas.create_line((col + 1) * third, row * third, col * third, (row + 1) * third)

        else:
            canvas.create_oval(col * third + 5, row * third + 5, (col + 1) * third - 5, (row + 1) * third - 5)
        turn += 1

canvas.pack()
canvas.bind("<Button-1>", mouse_click)
canvas.mainloop()

回答1:

The problem here is that the XsorOs object gets created every time you call the XorO method. This means XsorOs.turn is always 1. One way would be to keep track of turn from outside and call it with global but the use of global is something one should avoid especially it can get quite messy. I would recommend keeping track of turn within an own child class of Tk separate "logic" class

I made you an example for the latter:

(please note that this example is super sloppy (particularly the variable naming) and should just show you what I meant)

# stays the same until 'line4 = canvas.create_line(0, 400, 600, 400)'
class XsorOs:
    def __init__(self):
        self.turn = 1

    def click(self, row, col):
        if self.turn is (1 or 3 or 5 or 7 or 9):
            canvas.create_line(col * third, row * third, (col + 1) * third, (row + 1) * third)
            canvas.create_line((col + 1) * third, row * third, col * third, (row + 1) * third)
        else:
            canvas.create_oval(col * third + 5, row * third + 5, (col + 1) * third - 5, (row + 1) * third - 5)
            self.turn += 1


def mouse_click(c, event):
    col = int(event.x / third)
    row = int(event.y / third)
    c.click(row, col)


xo = XsorOs()
canvas.pack()
canvas.bind("<Button-1>", lambda event: mouse_click(xo, event))
canvas.mainloop()

EDIT:

  • lambda is basically a way to create one line functions. In this case, I used it to pass arguments through the event function. Because somewhere internally tkinter does something like if that mouseclick happens do passed_function(event) so you have no chance to use your own arguments. That's why lambda is useful here

  • __init__ is maybe not that important here since I saw people putting variables in class bodies before and apparently it works just fine, but I personally prefer it to create all the variables of a class in the constructor

  • self is like this in other languages a reference to the class or the object of that class (you can actually name it the way you want by naming the first constructor argument, but self is commonly used). It "pulls" the variable in the scope of the class instead of the function. That means the variable exists and can be manipulated as long as the object exists. A function basically loses everything after execution. That was the main problem in your prior code.



回答2:

I managed to fix it based off a few tweaks from your code. However, I could I make it that if a X is created, an O and X cannot be created on that same tile? Thanks for your help. This is what I have so far.

from tkinter import *


tk = Tk()
width = 600
third = width / 3
canvas = Canvas(width=width, height=width)
tk.title = "Tic Tac Toe"


line1 = canvas.create_line(200, 0, 200, 600)
line2 = canvas.create_line(400, 0, 400, 600)
line3 = canvas.create_line(0, 200, 600, 200)
line4 = canvas.create_line(0, 400, 600, 400)


class XsorOs:
    def __init__(self):
        self.turn = 0
        self.clicked = []

    def click(self, row, col):
        if (row, col) not in self.clicked
            if self.turn is 0:
                canvas.create_line(col * third, row * third, (col + 1) * third, (row + 1) * third)
                canvas.create_line((col + 1) * third, row * third, col * third, (row + 1) * third)
                self.turn += 1
            elif self.turn is 1:
                canvas.create_oval(col * third + 5, row * third + 5, (col + 1) * third - 5, (row + 1) * third - 5)
                self.turn -= 1
            else:
                print("Game Over")
            self.clicked.append((row, col))


def mouse_click(c, event):
    col = int(event.x / third)
    row = int(event.y / third)
    c.click(row, col)


xo = XsorOs()
canvas.pack()
canvas.bind("<Button-1>", lambda event: mouse_click(xo, event))
canvas.mainloop()

Also, if you have a reason to use the method you used that I was talking about in that comment, I would appreciate it if you would explain why.