Tkinter 行折叠井字游戏

问题描述 投票:0回答:1

与朋友一起完成了在 Tkinter 中创建井字游戏的教程。我们将其扩展为适用于 4x4 网格,并且希望不使用 X 和 O 来导入我们自己的自定义图像。我们已经成功地重新配置了代码,允许在单击网格方块时插入这些图像,但我们无法弄清楚为什么每当一行或一列被填充时,它就会将其折叠起来(而我们希望它保持原来的状态)尺寸相同)。有没有办法让所有单元格始终保持相同的大小?

https://realpython.com/tic-tac-toe-python/

import tkinter as tk #Currently 8.6
from tkinter import font
from itertools import cycle
from typing import NamedTuple

class Player(NamedTuple):
    # How to do as a 3D shape rather than flat shape
    label: str
    image: tk.PhotoImage
    color: str

class Move(NamedTuple):
    row: int
    col: int
    label: str = ""

#root = tk.Tk()
BOARD_SIZE = 4
#Need to change default player - technically one person doesn't have label

    
class TicTacToeGame:
    def __init__(self, board_size=BOARD_SIZE):
        self.board_size = board_size
        self.winner_combo = []
        self._current_moves = []
        self._has_winner = False
        self._winning_combos = []
        self._setup_board()
    
    def _setup_board(self):
        self._current_moves = [
            [Move(row, col) for col in range(self.board_size)]
            for row in range(self.board_size)
        ]
        self._winning_combos = self._get_winning_combos()
        
    def _get_winning_combos(self):
        rows = [
            [(move.row, move.col) for move in row]
            for row in self._current_moves
        ]
        columns = [list(col) for col in zip(*rows)]
        first_diagonal = [row[i] for i, row in enumerate(rows)]
        second_diagonal = [col[j] for j, col in enumerate(reversed(columns))]
        return rows + columns + [first_diagonal, second_diagonal]
    
    def is_valid_move(self, move):
        """Return True if move is valid, and False otherwise."""
        row, col = move.row, move.col
        move_was_not_played = self._current_moves[row][col].label == ""
        no_winner = not self._has_winner
        return no_winner and move_was_not_played
    
    def process_move(self, move):
        """Process the current move and check if it's a win."""
        row, col = move.row, move.col
        self._current_moves[row][col] = move
        for combo in self._winning_combos:
            #Set logic may not work for ours
            # [1,1,0,0], [0,1,0,1],...
            # Sum the arrays in the line
            # Check for any element being 0 or 4 (min/max statement on array)
            # Pascal 1,4,6,4,1 in terms of shared attributes (0 up to 4)
            results = set(
                self._current_moves[n][m].label
                for n, m in combo
            )
            is_win = (len(results) == 1) and ("" not in results)
            if is_win:
                self._has_winner = True
                self.winner_combo = combo
                break
                
    def has_winner(self):
        """Return True if the game has a winner, and False otherwise."""
        return self._has_winner
    
    def is_tied(self):
        """Return True if the game is tied, and False otherwise."""
        no_winner = not self._has_winner
        played_moves = (
            # Has the label of every cell been updated from empty i.e. piece played
            move.label for row in self._current_moves for move in row
        )
        return no_winner and all(played_moves)

    def reset_game(self):
        """Reset the game state to play again."""
        for row, row_content in enumerate(self._current_moves):
            for col, _ in enumerate(row_content):
                row_content[col] = Move(row, col)
        self._has_winner = False
        self.winner_combo = []

class TicTacToeBoard(tk.Tk):
    def __init__(self, game):
        super().__init__()
        self.title("TicTacToe Game")
        img = tk.PhotoImage(file="pieces/cyan_cuboid_small_striped.png").subsample(5)
        img2 = tk.PhotoImage(file="pieces/orange_cuboid_small_striped.png").subsample(5)
        players = (
            Player(label="X", image = img, color="blue"),
            Player(label="O", image = img2, color="green"),
        )
        self._players = cycle(players)
        self._cells = {}
        self._game = game
        self.current_player = next(self._players)
        self._create_menu()
        self._create_board_display()
        self._create_board_grid()
        

    def _create_board_display(self):
        display_frame = tk.Frame(master=self)
        display_frame.pack(fill=tk.X) #frame fills width if window resized
        self.display = tk.Label(
              master=display_frame, #label needs to live in frame
              text="Ready?",
              font=font.Font(size=28, weight="bold"),
        )
        self.display.pack()

    def _create_board_grid(self):
        grid_frame = tk.Frame(master=self) #Hold the game grid cells
        grid_frame.pack()
        for row in range(self._game.board_size):
            self.rowconfigure(row, weight=1, minsize=50)
            self.columnconfigure(row, weight=1, minsize=75)
            for col in range(self._game.board_size):
                button = tk.Button(
                    master=grid_frame,
                    text="",
                    font=font.Font(size=36, weight="bold"),
                    fg="black",
                    width=3,
                    height=2,
                    highlightbackground="lightblue",
                )
                self._cells[button] = (row, col)
                button.bind("<ButtonPress-1>", self.play)
                button.grid(
                    row=row,
                    column=col,
                    padx=5,
                    pady=5,
                    sticky="nsew"
                )

    def toggle_player(self):
        """Return a toggled player."""
        self.current_player = next(self._players)
    
    # The .mainloop() method on the Tk class runs what’s known as the application’s 
    # main loop or event loop. This is an infinite loop in which all the GUI events happen.
    def play(self, event):
        """Handle a player's move."""
        clicked_btn = event.widget
        row, col = self._cells[clicked_btn]
        move = Move(row, col, self.current_player.label)
        if self._game.is_valid_move(move): # Maybe show error message to user if False?
            self._update_button(clicked_btn)
            self._game.process_move(move)
            # Maybe check winner first rather than tied
            # Maybe only check winner after row/col/diagonal was filled
            # Technically first board_size - 1 moves don't need check
            if self._game.is_tied():
                self._update_display(msg="Tied game!", color="red")
            elif self._game.has_winner():
                self._highlight_cells()
                msg = f'Player "{self.current_player.label}" won!'
                color = self.current_player.color
                self._update_display(msg, color)
            else:
                self.toggle_player()
                msg = f"{self.current_player.label}'s turn"
                self._update_display(msg)
                
    def _update_button(self, clicked_btn):
        # Need to set to a 3D image for us
        clicked_btn.config(image=self.current_player.image)
        #clicked_btn.config(text=self.current_player.label)
        #clicked_btn.config(fg=self.current_player.color)
        
    def _update_display(self, msg, color="black"):
        self.display["text"] = msg
        self.display["fg"] = color
        
    def _highlight_cells(self):
        for button, coordinates in self._cells.items():
            if coordinates in self._game.winner_combo:
                button.config(highlightbackground="red")
                
    def _create_menu(self):
        menu_bar = tk.Menu(master=self)
        self.config(menu=menu_bar)
        file_menu = tk.Menu(master=menu_bar)
        file_menu.add_command(
            label="Play Again",
            command=self.reset_board
        )
        file_menu.add_separator()
        file_menu.add_command(label="Exit", command=quit)
        menu_bar.add_cascade(label="File", menu=file_menu)
        
    def reset_board(self):
        """Reset the game's board to play again."""
        self._game.reset_game()
        self._update_display(msg="Ready?")
        # Reset all the buttons
        for button in self._cells.keys():
            button.config(highlightbackground="lightblue")
            button.config(text="")
            button.config(fg="black")



def main():
    """Create the game's board and run its main loop."""
    game = TicTacToeGame()
    board = TicTacToeBoard(game)
    board.mainloop()

if __name__ == "__main__":
    main()

Image showing image icons being entered in grid Image showing problem of grid row collapsing when row is complete

python tkinter
1个回答
0
投票

这是因为您在这些按钮上设置了

height=2
。请注意,当只有文本时,它是按lines排列的。然而,当使用图像时,它是以像素为单位的。

因此,当有一个按钮没有图像时,同一行中的其他按钮将扩展到与该按钮相同的大小。 但是当同一行的所有按钮都使用图像时,按钮就会缩小。

您可以将这些按钮的

image
选项最初设置为空白图像,然后将
width
height
选项设置为所需的像素大小:

class TicTacToeBoard(tk.Tk):
    def __init__(self, game):
        super().__init__()
        self.title("TicTacToe Game")
        # create a blank image
        self.blank = tk.PhotoImage()
        ...

    def _create_board_grid(self):
        ...
        for row in range(self._game.board_size):
            ...
            for col in range(self._game.board_size):
                button = tk.Button(
                    master=grid_frame,
                    image=self.blank,  # use blank image
                    font=font.Font(size=36, weight="bold"),
                    fg="black",
                    width=100,    # set to desired size
                    height=100,
                    highlightbackground="lightblue",
                )
                ...
        ...
© www.soinside.com 2019 - 2024. All rights reserved.