I'm trying to program Tic-tac-toe. When I run my program, the X's and O's pop up in all sorts of strange locations, and they don't "settle" into their proper positions until the very last move of the game. Here is my code:

'''Tic-tac-toe.
'''

from Tkinter import *

SQU_SIZE = 100; DILATION = 3.5
BKGD = 'white'; FRGD = 'black'; LET_CLR = 'green'
THICK = 15; OFFSET = 5

'''Base frame.'''
class App(Frame):

    '''Constructor method.'''
    def __init__(self, master):
        Frame.__init__(self, master)
        self.grid()
        self.create_widgets()

    def show_letter(self, x,y, w,h):
        self.button[x][y].config(state = DISABLED)
        self.button[x][y].grid_remove()
        self.letter[x][y] = Canvas(width = SQU_SIZE, height = SQU_SIZE,\
                                   bg = 'white')#BKGD)
        if self.Xs_turn:
            self.letter[x][y].create_line(10,10, SQU_SIZE-OFFSET,\
                                          SQU_SIZE-OFFSET, width = THICK,\
                                          fill = FRGD)
            self.letter[x][y].create_line(SQU_SIZE-OFFSET,2*OFFSET,\
                                          2*OFFSET,SQU_SIZE-OFFSET,\
                                          width = THICK, fill = FRGD)
        else: #O's turn
            self.letter[x][y].create_oval(10,10, SQU_SIZE-OFFSET,\
                                          SQU_SIZE-OFFSET, width = THICK,\
                                          outline = FRGD, fill = BKGD)
        self.letter[x][y].grid(row = y, column = x)
        print y, x
        self.Xs_turn = not self.Xs_turn

    def create_widgets(self):
        self.Xs_turn = True #X goes first.
        #self.square = [] #The square frame.  Will become a 2D list.
        self.status = [] #Whether the square is blank, an X, or an O.
        self.button = [] #The button on the square.
        self.letter = []
        w = int(SQU_SIZE/5.55)
        h = int(SQU_SIZE/11.7)
        for i in range (3):
            #self.squareColumn = []
            self.statusColumn = []
            self.buttonColumn = []
            self.letterColumn = []
            for j in range (3):
                #miniFrame = Frame(self, width = w, height = h)
                #self.squareColumn.append(miniFrame)
                self.buttonColumn.append(Button(self, width = w, height = h,\
                                   bg = BKGD, fg = FRGD))
                #theButton.grid(row = j, column = i)
                self.buttonColumn[j].grid(row = j, column = i)
                #self.squareColumn[j].grid(row = j, column = i)
                self.letterColumn.append(None)
            #self.square.append(self.squareColumn)
            self.button.append(self.buttonColumn)
            self.letter.append(self.letterColumn)

        for i in range (3):
            for j in range (3):
                self.button[i][j]['command'] = lambda x=i, y=j:\
                                               self.show_letter(x,y, w,h)

def main():
    root = Tk()
    root.title('Tic-Tac-Toe')
    dimens = int(SQU_SIZE*DILATION)
    root.geometry(str(dimens) + 'x' + str(dimens))
    game = App(root)

    root.mainloop()

main()

You want to associate each button with a Tkinter StringVar. That way, you set (change) the contents of the StringVar and it is automatically displayed on the screeen. This is the start of a Tic-Tac_Toe game, to test button use, that I never finished. It uses a dictionary to keep track of the buttons and displays "X" or "O" via a StringVar, but doesn't have a scoring system.

from Tkinter import *

class ButtonsTest:
   def __init__(self, top):
      self.button_dic = {}
      top.title('Buttons Test')
      self.top_frame = Frame(top, width =500, height=500)
      self.buttons()
      self.top_frame.grid(row=0, column=1)
      exit = Button(self.top_frame, text='Exit', command=top.quit).grid(row=10,column=0, columnspan=5)

      self.player = True   ## True = "X", False = "O"

   ##-------------------------------------------------------------------         
   def buttons(self):
      b_row=1
      b_col=0
      for j in range(1, 10):
         self.button_dic[j] = StringVar()
         self.button_dic[j].set(str(j))
         b = Button(self.top_frame, textvariable = self.button_dic[j])
         b.grid(row=b_row, column=b_col)

         def handler ( event, self=self, button_num=j ):
                return self.cb_handler( event, button_num )
         b.bind ( "<Button-1>", handler )

         b_col += 1
         if b_col > 2:
            b_col = 0
            b_row += 1

   ##----------------------------------------------------------------
   def cb_handler( self, event, cb_number ):
      print "cb_handler", cb_number, self.button_dic[cb_number]                
      if self.player:          ## True = "X", False = "O"
         self.button_dic[cb_number].set("X")
      else:
         self.button_dic[cb_number].set("O")
      self.player = not self.player

##===================================================================
root = Tk()
BT=ButtonsTest(root)
root.mainloop()

Edited 6 Years Ago by woooee: n/a

But I don't want a StringVar; I want a Canvas image.

To place text on a canvas ('X' or 'O'), you use create_text(), not buttons, which may be why the text is not showing up in the correct place. You will have to use mouse position when clicked to determine where to place the 'X' or 'O'. And keep track of what is placed where to determine the winner of course.

from Tkinter import *

root = Tk()
root.title('Canvas Test')

##----------------------------------------------------------------------------
##--- generally, you only have on3 canvas object
canvas = Canvas(root, width =300, height=300)
##----------------------------------------------------------------------------

##----------------------------------------------------------------------------
##--- from here on, the single Canvas instance is used
##----------------------------------------------------------------------------
canvas.create_line(1, 100, 300, 100, width=3)
canvas.create_line(1, 200, 300, 200, width=3)
canvas.create_line(100, 1, 100, 300, width=3)
canvas.create_line(200, 1, 200, 300, width=3)

canvas.create_text(50,50, text='0', fill='blue', font=('verdana', 36))
canvas.create_text(150,150, text='X', fill='yellow', font=('verdana', 36))

canvas.pack()
root.mainloop()

Edited 6 Years Ago by woooee: n/a

But I don't want a StringVar; I want a Canvas image.

You could use concepts from this vegaseats post of clickable shapes:
http://www.daniweb.com/forums/post1322031.html#post1322031

You could tag all squares as named shapes and change another tag between '','X' and 'O' to mark what shape must be drawn there, when clicked, if square has been clicked you can remove the 'clickable' tag from it.

You could use concepts from this vegaseats post of clickable shapes:
http://www.daniweb.com/forums/post1322031.html#post1322031

You could tag all squares as named shapes and change another tag between '','X' and 'O' to mark what shape must be drawn there, when clicked, if square has been clicked you can remove the 'clickable' tag from it.

Good find. And Vegas does code some cherries.

Edited 6 Years Ago by woooee: n/a

In line 22, I forgot to put "self" as the first Canvas parameter. It works fine now.

This question has already been answered. Start a new discussion instead.