2

Currently I am trying to code Ultimate Tic Tac Toe on OSX and I read here that you cannot change the colors of a button on OSX. This leaves the GUI looking like this... enter image description here

I find the white buttons to be an eyesore and really take away from the game So is it possible to add a callback to an object other than a button. Something like this...

window.create_rectangle(x1,y1,x2,y2,callback = foo)
Community
  • 1
  • 1
mucle6
  • 645
  • 1
  • 10
  • 24
  • Are you trying to make a textbox from a drawn rectangle? Bad idea. – Malik Brahimi Jan 19 '15 at 21:05
  • @MalikBrahimi I don't think so, I think he's trying to let users click on the move they're trying to make, but style it so he doesn't have the wonky white buttons and has a matte black instead. – Adam Smith Jan 19 '15 at 21:07
  • @AdamSmith Thats exactly what I'm trying to do. I figure that there has to be some sort of way to program a game such that you don't have to have platform specific buttons – mucle6 Jan 19 '15 at 21:08
  • @MalikBrahimi I'm not trying to make a text box, I'm just trying to make a clickable "thing" that isn't a button – mucle6 Jan 19 '15 at 21:10
  • @mucle6 you could probably create an event listener looking for a mouse click, grab the event and track it to which square it's pointed at, then draw an "X" or an "O" at that location. It's not trivial though. – Adam Smith Jan 19 '15 at 21:11
  • So how's my solution below? – Malik Brahimi Jan 19 '15 at 21:26

3 Answers3

2

If you're drawing a rectangle at with the coordinates (a, b) and (c, d):

def callback(event):
    if a <= event.x <= c:
        if b <= event.y <= d:
            print 'Rectangle clicked' # change rect here

window.bind('<ButtonPress-1>', callback)
Malik Brahimi
  • 16,341
  • 7
  • 39
  • 70
2

If rects is a list of rectangles:

rects = [] # contains rects

def callback(event):
    for rect in rects:
        if rect.a <= event.x <= rect.c:
            if rect.b <= event.y <= rect.d:
                rect.change()

window.bind('<ButtonPress-1>', callback)
Malik Brahimi
  • 16,341
  • 7
  • 39
  • 70
  • Still a ton of compares. It could be faster, but it works :). I'd use a `global` call at the beginning of the callback func so it's explicit you're talking about the globally scoped `rects`. I'd probably do it algebraically, something like `global rects; id_of_rect = event.x % space_between_rects + 9 * event.y % space_between_rects; rects[id_of_rect].change()` – Adam Smith Jan 19 '15 at 21:44
  • This perfectly answers my question! That said,to avoid confusion, this can be used without generating anything visual and just haivng an array of coordinates that represent where you want a user to click. For example if you used a picture as the background that mapped out what you wanted, you could code in invisible boxes – mucle6 Jan 19 '15 at 21:56
  • @AdamSmith `global` isn't necessary because we're not changing `rects` – Malik Brahimi Jan 19 '15 at 22:04
  • @MalikBrahimi It's not necessary, but `explicit is better than implicit` – Adam Smith Jan 21 '15 at 16:04
2

Yes, you can put a binding on any widget. A common thing to do is use a label widget, though you can also use a canvas and draw any shape you want. If you store all of the widgets in a dictionary using the row and column as a key, it's very easy for the callback to know what you clicked on (though, of course, event.widget will also tell you).

Here's a quick example that uses labels, and illustrates how to pass the row and column into a callback. It just creates a single tic-tac-toe board for brevity:

import Tkinter as tk

class Example(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent, background="bisque")
        self.canvas = tk.Canvas(self, width=400, height=400)

        board = TicTacToe(self)
        board.pack(padx=20, pady=20)

class TicTacToe(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent, background='black')

        self.player = "X"
        self.cell = {}
        for row in range(3):
            for col in range(3):
                cell = tk.Label(self, background="darkgray", foreground="white", width=2)
                cell.bind("<1>", lambda event, col=col, row=row: self.on_click(row, col))
                cell.grid(row=row, column=col, sticky="nsew", padx=1, pady=1)
                self.cell[(row,col)] = cell

    def on_click(self, row, col):
        current = self.cell[(row, col)].cget("text")
        if current == "X" or current == "O":
            self.bell()
            return
        self.cell[(row, col)].configure(text=self.player)
        self.player = "X" if self.player == "O" else "O"

if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685