如何用GUI(用Tkinter)和逻辑组件组织代码?

2024-06-01 00:34:25 发布

您现在位置:Python中文网/ 问答频道 /正文

我在做一个小动作。所以我有一个代表游戏状态的类TicTacToe,这个类显示了在游戏中操作的方法(网格,谁拥有哪些单元,谁是下一个要玩的玩家)。这是合乎逻辑的部分。在

我想用Tkinter(作为tk导入)将其放入GUI中。我在几个方面犹豫不决:

  1. 创建一个TicTacToeUI类,该类继承tk.Frame和{};

  2. 创建一个继承TicTacToeUI并具有TicTacToe属性的TicTacToeUI类;

  3. 创建一个继承TicTacToeUI的类,它只定义图形元素,TicTacToeBindingUI将{}的实例与{}的实例绑定。

这是用Tkinter分离逻辑和GUI的惯用方法吗?在

下面是我编写的代码;它是用法语注释的,但是变量的名称似乎足够清楚,可以理解这个过程。如你所见,我选择第二种解决方案。欢迎评论。在

from tkinter import *

class AlreadyTaken(Exception):
    pass

class GameOver(Exception):
    pass

class TicTacToe:
    def __init__(self):
        """ Built an object representing the state of the TicTacToe.
        An instance of TicTacToe has two attributes:
        self.next_player_to_play : indicate who is the next player (coding by 1 or 2).
        self.grid (list of list): the grid 3x3 of the game; 0 is for
        empty cells, 1 (resp. 2) is for the first (resp. second player).
        """
        self.grid = [[0] * 3 for i in range(3)]
        self.next_player_to_play = 1
        self.game_over = False

    def is_cell_free(self, i, j):
        """ Return True if the cell (i, j) is empty, False orthewise.
        Raises IndexError if i and j are not in [0,2]."""
        if not ( 0 <= i <= 2 and 0 <= j <= 2):
            raise IndexError('cell (%d, %d) index out of range'%(i, j))
        return not self.grid[i][j]

    def play(self, i, j):
        """ The player self.next_player_to_play capture the cell (i, j), 
        if this cell is free. Raises AlreadyTaken exception if (i, j)
        is not empty, or GameOver if the game is over.
        Return 0 if the current player doesn't win, or the number of
        the winning player."""
        if self.game_over:
            raise GameOver('the game is over, the winner is the player %d'
                            %self.next_player_to_play)

        if not self.is_cell_free( i, j):
            raise AlreadyTaken('cell (%d, %d) already taken by player %d'
                                    %(i, j, self.grid[i][j]))
        p = self.next_player_to_play
        self.grid[i][j] = p
        if self._is_victorious(p):
            self.game_over = True
            return p
        # switch les deux joueurs
        self.next_player_to_play = 3 - p
        return 0

    def _is_victorious(self, player):
        """ Return True if the player `player` wins, i.e. if he succeed making a row, a column or a diagonal."""
        g = self.grid
        return (
         # fill a column?
         any(all(g[i][j] == player for i in range(3)) for j in range(3)) or
         # fill a row ?
         any(all(g[i][j] == player for j in range(3)) for i in range(3)) or
         # fill a diagonal ?
         all(g[i][i] == player for i in range(3)) or
         # l'anti-diagonale ?
         all(g[i][2-i] == player for i in range(3))
        )


class TicTacToeUI(Frame):
    def __init__(self, 
                 master=None,
                 void_symb=' ', 
                 player1_symb='X', 
                 player2_symb='O', 
                 tictactoe=None):
        """ Build a frame drawing a TicTacToe game with 9 cells.

        * `<situation>_symb`: indicates what symbol to use for a void cell, or owned by a player (1 or 2).
        * `tictactoe`: by default (if `tictactoe` is None),a new game is created, 
        but we can use an existing TicTacToe instance instead."""

        super().__init__(master, width=150, height=150)

        if tictactoe is None:
            tictactoe = TicTacToe()
        self.tictactoe = tictactoe
        symbols = [void_symb, player1_symb, player2_symb]
        self._symbols = symbols
        buttons_grid = []
        self.buttons_grid = buttons_grid        

        Label(self, text='Super tictactoe!').grid(row=0, column=0, columnspan=3)

        label_player_playing = Label(self, text='Player 1 is playing')
        label_player_playing.grid(row=1, column=0, columnspan=3)
        self.label_player_playing = label_player_playing

        for i in range(3):
            buttons_line = []
            for j in range(3):
                player = tictactoe.grid[i][j]
                button = Button(
                    self,
                    text=symbols[player],
                    command=lambda i=i, j=j: self.play(i, j),
                    bg="green"
                )
                button.grid(row=i+2, column=j)
                buttons_line.append(button)
            buttons_grid.append(buttons_line)
        Button(
            self, 
            text='Start a new game', 
            command=self.reinit_game
            ).grid(column=0, row=5, columnspan=3)

    def play(self, i, j):
        try:
            player = self.tictactoe.next_player_to_play
            victorious_player = self.tictactoe.play(i, j)
            self.buttons_grid[i][j]['text'] = self._symbols[player]

            if victorious_player:
                self.label_player_playing['text'] = 'Player %d wins!' % player
            else:
                self._update_label_player_playing()
        except (AlreadyTaken, GameOver) as e:
            pass

    def _update_label_player_playing(self):
        player = self.tictactoe.next_player_to_play
        self.label_player_playing['text'] = 'Player %d is playing' % player

    def _all_buttons_to_void(self):
        for line in self.buttons_grid:
            for b in line:
                b['text'] = self._symbols[0]

    def reinit_game(self):
        self.tictactoe = TicTacToe()
        self._all_buttons_to_void()
        self._update_label_player_playing()


m = TicTacToeUI()
m.pack()
m.mainloop()

Tags: thetoinselfgameforplayif