class OthelloBoardApp: def __init__(self, input_data): ''' Initializes the GUI board and the Othello model ''' # store input data num_rows = input_data.get_num_rows() num_cols = input_data.get_num_cols() self._next_player = input_data.get_first_player() top_left_color = input_data.get_top_left_color() self._who_wins = input_data.get_who_wins() # set up the GUI: Canvas and labels self._root_window = tkinter.Tk() self._root_window.title("Othello - FULL") self._board = Board(num_rows, num_cols) self._board_width, self._board_height = self.board_size( num_rows, num_cols) self._canvas = tkinter.Canvas(master=self._root_window, width=self._board_width, height=self._board_height, background=_BACKGROUND_COLOR) self._canvas.grid(row=0, column=0, padx=2, pady=2, sticky=tkinter.N + tkinter.S + tkinter.E + tkinter.W) self._next_turn = tkinter.StringVar() self._turn_label = tkinter.Label(master=self._root_window, textvariable=self._next_turn, font=_DEFAULT_FONT) self._turn_label.grid(row=1, column=0, padx=2, pady=2, sticky=tkinter.W) self._disc_counts = tkinter.StringVar() self._count_label = tkinter.Label(master=self._root_window, textvariable=self._disc_counts, font=_DEFAULT_FONT) self._count_label.grid(row=2, column=0, padx=2, pady=2, sticky=tkinter.W) # set up window resizing self._root_window.columnconfigure(0, weight=1) self._root_window.rowconfigure(0, weight=1) # set up callback handlers for button release and configuration self._canvas.bind('<ButtonRelease-1>', self._on_button_down) self._canvas.bind('<Configure>', self._on_canvas_resize) # set up Othello model self._game_over = False self._othello = Othello(num_rows, num_cols, self._next_player) self._othello.setBoard(top_left_color) self._process_next_move() def run(self) -> None: ''' Runs the Othello application taking user inputs ''' self._root_window.mainloop() def board_size(self, num_rows, num_cols) -> (int, int): ''' Returns the size of the board in pixel units Draws bigger cells when number of columns is less than 4 so that the complete title of the window is displayed. ''' if (num_cols == 4): return (num_cols * 70, num_rows * 70) else: return (num_cols * 50, num_rows * 50) def _on_button_down(self, event: tkinter.Event): ''' Event handler for left mouse button release event ''' if self._game_over == False: canvas = event.widget x = canvas.canvasx(event.x) y = canvas.canvasy(event.y) #print ((event.x, event.y), (x,y)) player_input = self._board.cell_location( point.from_pixel(x, y, self._board_width, self._board_height)) try: if self._othello._validateInput(player_input, self._next_player): self._othello.updateBoard(player_input, self._next_player) self._process_next_move() except InvalidMoveError: pass # nothing to do if invalid move def _process_next_move(self): ''' Processes the next move by calling the Othello model. Called after initial board set up and after every uesr selection ''' #self.printBoard() self._draw_board(self._othello.getBoard()) if self._othello.continuePlaying(): # game still continuing self._next_player = self._othello.getNextPlayer() self._next_turn.set('Next Turn: ' + self.display_color(self._next_player)) else: # game over. determine and print winner self._game_over = True self._next_turn.set("WINNER: " + self._winner()) # set the tile count for both ongoing or end game num_tiles = self._othello.determineNumberTiles() self._disc_counts.set('Black: ' + str(num_tiles[_BLACK]) + ' White: ' + str(num_tiles[_WHITE])) def _winner(self) -> str: ''' Determines the winner by comparing the tile counts against the winner criteria provided as input to the game. ''' num_tiles = self._othello.determineNumberTiles() winner = "None" if (self._who_wins == ">"): # higher count wins if (num_tiles[_BLACK] < num_tiles[_WHITE]): winner = "White" elif (num_tiles[_BLACK] > num_tiles[_WHITE]): winner = "Black" elif (self._who_wins == "<"): # lower count wins if (num_tiles[_BLACK] > num_tiles[_WHITE]): winner = "White" elif (num_tiles[_BLACK] < num_tiles[_WHITE]): winner = "Black" return winner def _on_canvas_resize(self, event: tkinter.Event): ''' Handler for canvas resize. Redraws the entire board with current dimensions ''' self._canvas.delete(tkinter.ALL) self._board_width = self._canvas.winfo_width() self._board_height = self._canvas.winfo_height() self._draw_board(self._othello.getBoard()) def _draw_board(self, board_info) -> None: ''' Draw the Othello game board First draws the vertical and horizontal grid lines on the canvas Then draws the currently available black and white discs from the Othello model ''' board_size = self._board.board_size() next_x = 0 for vert_line in range(board_size[1]): # print((next_x,0,next_x,self._board_height)) self._canvas.create_line(next_x, 0, next_x, self._board_height, fill='red', width='2.0') next_x = next_x + (self._board_width / board_size[1]) next_y = 0 for hor_line in range(board_size[0]): # print((0, next_y, self._board_width, next_y,)) self._canvas.create_line(0, next_y, self._board_width, next_y, fill='red', width='2.0') next_y = next_y + (self._board_height / board_size[0]) for row in range(board_size[0]): for col in range(board_size[1]): cell = self._board.cell_from_loc(row, col) self._draw_disc(cell, self.display_color(board_info[row][col])) def display_color(self, color_code: str) -> str: ''' Translates the color code understood by Othello model to readable string for GUI display ''' if (color_code == 'W'): return 'White' elif (color_code == 'B'): return 'Black' else: return None def _draw_disc(self, cell: Cell, color: str) -> None: ''' Draws the disc of the provided color in the given Cell object instance ''' if (color != None): disc_coords = cell.disc_coords() top_left = disc_coords[0].pixel(self._board_width, self._board_height) bottom_right = disc_coords[1].pixel(self._board_width, self._board_height) self._canvas.create_oval(top_left[0], top_left[1], bottom_right[0], bottom_right[1], fill=color, outline='black') def printBoard(self): #prints the board on console - for debug purposes only boardInfo = self._othello.getBoard() for row in boardInfo: rowStr = "" for col in row: rowStr += col + " " print(rowStr)