1

I'm currently working on a Python GUI version of Reversi for a programming class. I've already programmed the game logic, and I'm currently trying to implement the GUI using Tkinter. I'm having some problems with resizing the game board (root window) and everything on it (canvases and shapes) proportionally. The game currently works, but everything that I've tried to get the board to resize correctly hasn't worked. The relevant code is as follows.

class OthelloApplication:
    def __init__(self, game_state: othello.Othello):
        self._game_state = game_state
        self._game_state.new_game()
        self._game_board = game_state.show_board()
        self._root_window = tkinter.Tk()

        for row in range(self._game_state.show_rows()):
            for col in range(self._game_state.show_cols()):
                canvas = tkinter.Canvas(self._root_window, width = 100, height = 100, 
                    borderwidth = 0, highlightthickness = 1, 
                    background = _BACKGROUND_COLOR, highlightbackground = 'black')
                canvas.grid(row = row, column = col, padx = 0, pady = 0,
                    sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
                self._root_window.rowconfigure(row, weight = 1)
                self._root_window.columnconfigure(col, weight = 1)

        self._turn_window = tkinter.Canvas(self._root_window, width = 200,
            height = 100, highlightthickness = 0, background = 'white')

        self._root_window.bind('<Button-1>', self._on_canvas_clicked)
        self._root_window.bind('<Configure>', self.on_resize)



    def draw_game_pieces(self) -> None:
        for row in range(self._game_state.show_rows()):
            for col in range(self._game_state.show_cols()):
                if self._game_board[col][row] == ' ':
                    pass
                else:
                    canvas = tkinter.Canvas(master = self._root_window, width = 100, height = 100, 
                        borderwidth = 0, highlightthickness = 1,
                        background = _BACKGROUND_COLOR, highlightbackground = 'black')
                    canvas.grid(row = row, column = col, padx = 0, pady = 0,
                        sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
                    canvas.update()
                    canvas.create_oval(2, 2, canvas.winfo_width() - 2,
                        canvas.winfo_height() - 2, fill = self._which_color(col,
                        row), outline = self._which_color(col, row))
                    self._root_window.rowconfigure(row, weight = 1)
                    self._root_window.columnconfigure(col, weight = 1)



    def display_turn(self) -> None:
        if self._game_state.show_turn() == 'B':
            turn = 'black'
        else:
            turn = 'white'
        self._turn_window.grid(row = self._game_state.show_rows() // 2 - 1, 
            column = self._game_state.show_cols() + 1,
            sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
        self._turn_info = self._turn_window.create_text(10, 10, 
            font = 'Helvetica', anchor = 'nw')
        self._turn_window.itemconfig(self._turn_info, text = 'Turn = ' + turn)



    def on_resize(self, event: tkinter.Event) -> None:
        self.draw_game_pieces()



    def _which_color(self, col: int, row: int) -> str:
        if self._game_board[col][row] == 'B':
            return 'black'
        elif self._game_board[col][row] == 'W':
            return 'white'



    def _on_canvas_clicked(self, event: tkinter.Event) -> (int):
        print(event.widget.winfo_reqwidth(), event.widget.winfo_reqheight())
        print(event.widget.winfo_width(), event.widget.winfo_height())
        try:
            grid_info = event.widget.grid_info()
            move = (int(grid_info["row"]), int(grid_info["column"]))
            self._game_state.player_move(move[1], move[0])
            self.draw_game_pieces()
            self.display_turn()
        except AttributeError:
            pass
        except othello.InvalidMoveError:
            print('Error: that wasn\'t a valid move.')
        except othello.GameOverError:
            print('The game is over.')



    def start(self) -> None:
        self.draw_game_pieces()
        self.display_turn()
        self._root_window.mainloop()

The draw_game_pieces() method draws the appropriately colored circle in the correct space on the board based on the size of the canvas on which it is being drawn.

My solution to my resize problem was binding on_resize() to the '<Configure>' event in the init method, but that causes the program to crash in a cycle of recursions. I'm new to tkinter and GUIs in general. Why is the on_resize() method being bound to '<Configure>' causing the program to crash?

Sorry about the messy code, I'm very much still working on it.

Thanks.

1 Answer 1

0

The crash due to recursion is probably because your on_resize method creates new widgets. Here's what's happening:

  1. the widget gets a event which ...
  2. calls on_resize which ...
  3. creates a nested loop which...
  4. creates a single canvas, which ...
  5. causes a configuration change in the widget
  6. you call update inside the nested loop which ...
  7. causes the event to be processed, which ...
  8. calls on_resize, which...
  9. creates a nested loop which ...
  10. creates a canvas which ...
  11. causes a configuration change in the widget
  12. you call update inside the nested loop which ...
  13. causes the event to be processed, which ...
  14. calls on_resize, which ...
  15. ...

As a rule of thumb you should never call update unless you are absolutely certain you need to, and even then you need to stop and think hard about it. A rule that should almost never, ever be broken is to call update in a function that is called in response to an event. Every time you call update it's almost as if you're calling mainloop again -- it creates a nested event loop which tries to process all pending events. If the processing of pending events causes new events to be generated, you'll end up with the recursive situation you now find yourself in.

The first fix is to remove the call to update. The second fix is probably to not create new widgets in the handling of the configure event. You really only need to create all of those widgets once. The redraw event only needs to redraw the ovals, because the canvas objects will resize themselves automatically.

My advice is to step back and take it slower. Rewrite your program to just draw the board without worrying about the ovals or game logic. Get the resize behavior of the board working the way you want, and then worry about redrawing all of the ovals.

Sign up to request clarification or add additional context in comments.

1 Comment

Based on what I read about update, I thought something like that might be happening. You were right about both fixes. After deleting update, I stored each canvas in a dictionary and just operated on the existing canvases that way. Everything works smoothly, thanks for the help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.