python - 如何在井字游戏 PyQT5 中显示获胜棋步?

标签 python python-3.x pyqt5

我目前正在熟悉强化学习基础知识,为了方便起见(而不是在终端中手动输入坐标),我创建了一个非常简单的 UI 来测试训练有素的代理和玩游戏。出于测试目的,我创建了一种方法,该方法在人类玩家单击单元格后生成随机移动,并且在单击/随机移动之后显示每个移动,但是当移动是获胜移动时,它不会显示并且棋盘会重置。我不知道为什么会这样,但看起来执行顺序不是顺序的/有些我不明白。我尝试使用 time.sleep(1)在板更新以确保我不会忽略实际显示的移动但移动永远不会显示后,窗口只是卡住。
预期结果:
ex
我实际得到的(假设我将单击单元格 (0, 2)):
ac

from PyQt5.QtWidgets import (
    QMainWindow,
    QDesktopWidget,
    QApplication,
    QWidget,
    QHBoxLayout,
    QVBoxLayout,
    QPushButton,
    QLabel,
)
from PyQt5.QtCore import Qt
import numpy as np
import sys


class TicCell(QPushButton):
    """
    Tic Tac Toe cell.
    """

    def __init__(self, location):
        """
        Initialize cell location.
        Args:
            location: Tuple, (row, col).
        """
        super().__init__()
        self.location = location


class TicUI(QMainWindow):
    """
    Tic Tac Toe interface.
    """

    def __init__(
        self,
        window_title='Smart Tic Tac Toe',
        board_size=3,
        empty_value=0,
        x_value=1,
        o_value=2,
        agent=None,
    ):
        """
        Initialize game settings.
        Args:
            window_title: Display window name.
            board_size: int, the board will be of size(board_size, board_size).
            empty_value: int representation of an empty cell.
            x_value: int representation of a cell containing X.
            o_value: int representation of a cell containing O.
            agent: Trained TicAgent object.
        """
        super().__init__()
        self.setWindowTitle(window_title)
        self.board_size = board_size
        self.empty_value = empty_value
        self.x_value = x_value
        self.o_value = o_value
        self.agent = agent
        self.text_map = {
            x_value: 'X',
            o_value: 'O',
            empty_value: '',
            'X': x_value,
            'O': o_value,
            '': empty_value,
        }
        win_rectangle = self.frameGeometry()
        center_point = QDesktopWidget().availableGeometry().center()
        win_rectangle.moveCenter(center_point)
        self.central_widget = QWidget(self)
        self.main_layout = QVBoxLayout()
        self.score_layout = QHBoxLayout()
        self.human_score = 0
        self.agent_score = 0
        self.score_board = QLabel()
        self.update_score_board()
        self.setStyleSheet('QPushButton:!hover {color: yellow}')
        self.cells = [
            [TicCell((c, r)) for r in range(board_size)]
            for c in range(board_size)
        ]
        self.cell_layouts = [QHBoxLayout() for _ in self.cells]
        self.board = np.ones((board_size, board_size)) * self.empty_value
        self.adjust_layouts()
        self.adjust_cells()
        self.update_cell_values()
        self.show()

    def adjust_layouts(self):
        """
        Adjust score board and cell layouts.

        Returns:
            None
        """
        self.main_layout.addLayout(self.score_layout)
        for cell_layout in self.cell_layouts:
            self.main_layout.addLayout(cell_layout)
        self.central_widget.setLayout(self.main_layout)
        self.setCentralWidget(self.central_widget)

    def adjust_cells(self):
        """
        Adjust display cells.

        Returns:
            None
        """
        self.score_layout.addWidget(self.score_board)
        self.score_board.setAlignment(Qt.AlignCenter)
        for row_index, row in enumerate(self.cells):
            for cell in row:
                cell.setFixedSize(50, 50)
                cell.clicked.connect(self.game_step)
                self.cell_layouts[row_index].addWidget(cell)

    def get_empty_cells(self):
        """
        Get empty cell locations.

        Returns:
            A list of indices that reepresent currently empty cells.
        """
        empty_locations = np.where(self.board == self.empty_value)
        empty_locations = list(zip(empty_locations[0], empty_locations[1]))
        for empty_location in empty_locations:
            r, c = empty_location
            cell_text = self.cells[r][c].text()
            assert cell_text == self.text_map[self.empty_value], (
                f'location {empty_location} has a cell value of {cell_text}'
                f'and board value of {self.board[r][c]} {self.board}'
            )
        return empty_locations

    def update_score_board(self):
        """
        Update the display scores.

        Returns:
            None
        """
        self.score_board.setText(
            f'Human {self.human_score} - ' f'{self.agent_score} Agent'
        )

    def check_win(self, player_value):
        """
        Check current game state for winner.
        Args:
            player_value: int, self.x_value or self.o_value.

        Returns:
            True if player_value won, False otherwise.
        """
        return (
            np.all(self.board == player_value, axis=0).any()
            or np.all(self.board == player_value, axis=1).any()
            or np.all(self.board.diagonal() == player_value)
            or np.all(self.board[::-1].diagonal() == player_value)
        )

    def reset_cell_colors(self):
        """
        Reset display cell text colors.

        Returns:
            None
        """
        for row_idx in range(self.board_size):
            for col_idx in range(self.board_size):
                self.cells[row_idx][col_idx].setStyleSheet('color: yellow')

    def reset_game(self, winner=None):
        """
        Reset board and display cells and update display scores.
        Args:
            winner: int, self.x_value or self.o_value.

        Returns:
            None
        """
        self.board = (
            np.ones((self.board_size, self.board_size)) * self.empty_value
        )
        self.update_cell_values()
        if winner == self.x_value:
            self.human_score += 1
        if winner == self.o_value:
            self.agent_score += 1
        self.update_score_board()
        self.reset_cell_colors()

    def modify_step(self, cell_location, value):
        """
        Modify board and display cells.
        Args:
            cell_location: tuple, representing indices(row, col).
            value: int, self.x_value or self.o_value.

        Returns:
            True if the clicked cell is not empty, None otherwise.
        """
        r, c = cell_location
        board_value = self.board[r, c]
        cell_value = self.cells[r][c].text()
        if not board_value == self.empty_value:
            return True
        assert cell_value == self.text_map[self.empty_value], (
            f'mismatch between board value({board_value}) '
            f'and cell value({cell_value}) for location {(r, c)}'
        )
        if value == self.x_value:
            self.cells[r][c].setStyleSheet('color: red')
        self.board[r, c] = value
        self.cells[r][c].setText(f'{self.text_map[value]}')

    def game_step(self):
        """
        Post cell-click step(human step and agent step)

        Returns:
            None
        """
        cell = self.sender()
        stop = self.modify_step(cell.location, self.x_value)
        if stop:
            return
        x_win = self.check_win(self.x_value)
        if x_win:
            self.reset_game(self.x_value)
            return
        empty_locations = self.get_empty_cells()
        if not empty_locations:
            self.reset_game()
            return
        choice = np.random.choice(range(len(empty_locations)))
        if self.agent:
            choice = self.agent.generate_move(self.board, empty_locations)
        self.modify_step(empty_locations[choice], self.o_value)
        o_win = self.check_win(self.o_value)
        if o_win:
            self.reset_game(self.o_value)

    def update_cell_values(self):
        """
        Sync display cells with self.board

        Returns:
            None
        """
        for row_index, row in enumerate(self.board):
            for col_index, col in enumerate(row):
                update_value = self.text_map[self.board[row_index][col_index]]
                self.cells[row_index][col_index].setText(f'{update_value}')


if __name__ == '__main__':
    test = QApplication(sys.argv)
    window = TicUI()
    sys.exit(test.exec_())

最佳答案

在大多数 GUI 工具包中,您从未真正直接在屏幕上绘图。相反,正在发生的事情是您要么绘制到辅助缓冲区,要么只更新小部件的对象模型,然后当您将控制权交给工具包的主循环​​时,工具包会将其复制到实际屏幕。
如果要更新实际界面,则需要从回调方法返回。因此,如果您想更新屏幕并在几秒钟后再次更新,您需要在设置计时器后从第一个回调返回以进行回调以进行第二次屏幕更新。在 QT 中,您可以为此使用 QTimer。

关于python - 如何在井字游戏 PyQT5 中显示获胜棋步?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62591411/

相关文章:

Python 3 - 垂直线内的 matplitlib 文本

python - 前一个 GUI 框架的 'Back' 按钮的 PyQt 适当函数

python - 重写的 QGraphicsView keyPressEvent 从聚焦的 QGraphicsWidgets 窃取事件

python - 如何在 QMessageBox 小部件中居中文本和按钮

python - numpy 中的数组索引

python - 重新调用函数(递归)与在 Python 中使用 while 语句

python - 导入错误 : cannot import name wraps on Mac

python - 如何为 python 3.6 安装 lpsolve?

python - 在 n 行后在 pandas 中求和

python-3.x - QuantLib:建立关键利率风险